from __future__ import annotations

import json
import tempfile
import unittest
from pathlib import Path

from scienith_openapi_contract_tooling.openapi.bundle import bundle_openapi_spec_for_docs
from scienith_openapi_contract_tooling.openapi.msw_handlers_ts import openapi_to_msw_handlers_ts
from scienith_openapi_contract_tooling.openapi.python_models import _collect_optional_non_nullable_array_fields
from scienith_openapi_contract_tooling.openapi.zod_ts import openapi_to_zod_ts


class OpenApiGeneratorTests(unittest.TestCase):
    def write_fixture(self, root: Path) -> Path:
        (root / "schemas").mkdir()
        (root / "schemas" / "item.json").write_text(
            json.dumps(
                {
                    "type": "object",
                    "required": ["id"],
                    "properties": {
                        "id": {"type": "string", "format": "uuid"},
                        "tags": {"type": "array", "items": {"type": "string"}},
                        "status": {"enum": ["active", "archived"]},
                        "nickname": {"type": ["string", "null"]},
                    },
                    "additionalProperties": False,
                }
            ),
            encoding="utf-8",
        )
        openapi = {
            "openapi": "3.1.0",
            "info": {"title": "Fixture API", "version": "1.0.0"},
            "paths": {
                "/items/{item_id}": {
                    "get": {
                        "operationId": "getItem",
                        "responses": {
                            "200": {
                                "description": "ok",
                                "content": {
                                    "application/json": {
                                        "schema": {"$ref": "#/components/schemas/Item"}
                                    }
                                },
                            }
                        },
                    }
                }
            },
            "components": {"schemas": {"Item": {"$ref": "schemas/item.json"}}},
        }
        root_path = root / "openapi.json"
        root_path.write_text(json.dumps(openapi), encoding="utf-8")
        return root_path

    def test_bundle_rewrites_external_schema_ref(self) -> None:
        with tempfile.TemporaryDirectory() as td:
            root_path = self.write_fixture(Path(td))
            bundled = bundle_openapi_spec_for_docs(root_path=root_path)
        self.assertEqual(bundled["components"]["schemas"]["Item"]["type"], "object")
        response_schema = bundled["paths"]["/items/{item_id}"]["get"]["responses"]["200"]["content"][
            "application/json"
        ]["schema"]
        self.assertEqual(response_schema, {"$ref": "#/components/schemas/Item"})

    def test_zod_handles_nullable_union_and_optional_array(self) -> None:
        with tempfile.TemporaryDirectory() as td:
            root_path = self.write_fixture(Path(td))
            out_path = Path(td) / "openapi.zod.ts"
            openapi_to_zod_ts(root_path=str(root_path), out_path=str(out_path), bundle=True)
            text = out_path.read_text(encoding="utf-8")
        self.assertIn("export const ItemSchema", text)
        self.assertIn('"tags": z.array(z.string()).optional()', text)
        self.assertIn('"nickname": z.union([z.string(), z.null()]).optional()', text)

    def test_msw_generates_path_params_and_sample_response(self) -> None:
        with tempfile.TemporaryDirectory() as td:
            root_path = self.write_fixture(Path(td))
            out_path = Path(td) / "handlers.generated.ts"
            openapi_to_msw_handlers_ts(root_path=str(root_path), out_path=str(out_path), bundle=True)
            text = out_path.read_text(encoding="utf-8")
        self.assertIn('http.get("/items/:item_id"', text)
        self.assertIn('"id": "00000000-0000-0000-0000-000000000000"', text)

    def test_python_optional_array_patch_target_detection(self) -> None:
        spec = {
            "components": {
                "schemas": {
                    "Item": {
                        "type": "object",
                        "required": ["id"],
                        "properties": {
                            "id": {"type": "string"},
                            "tags": {"type": "array", "items": {"type": "string"}},
                            "nullable_tags": {
                                "type": ["array", "null"],
                                "items": {"type": "string"},
                            },
                        },
                    }
                }
            }
        }
        self.assertEqual(_collect_optional_non_nullable_array_fields(spec), {"Item": {"tags"}})


if __name__ == "__main__":
    unittest.main()

