import importlib
import importlib.util
import sys
from pathlib import Path
from typing import Annotated, Any

import pytest

from iisi_app_core import ComponentLifetime, PortsAndAdaptersViolationError


def _create_pkg_file(path: Path, content: str = "") -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")


def test_fastapi_integration_import_explains_missing_optional_dependencies(monkeypatch: pytest.MonkeyPatch) -> None:
    import iisi_app_core.integrations  # noqa: F401

    module_name = "iisi_app_core.integrations.fastapi"
    source_path = Path(__file__).parents[1] / "src" / "iisi_app_core" / "integrations" / "fastapi.py"
    original_import_module = importlib.import_module

    def fake_import_module(name: str, package: str | None = None) -> Any:
        if name in {"fastapi", "mangum"}:
            raise ImportError(f"missing {name}")
        return original_import_module(name, package)

    monkeypatch.delitem(sys.modules, module_name, raising=False)
    monkeypatch.setattr(importlib, "import_module", fake_import_module)

    spec = importlib.util.spec_from_file_location(module_name, source_path)
    assert spec is not None
    assert spec.loader is not None
    module = importlib.util.module_from_spec(spec)
    sys.modules[module_name] = module

    with pytest.raises(ImportError, match='iisi-app-core\\[fastapi\\]'):
        spec.loader.exec_module(module)

    sys.modules.pop(module_name, None)


@pytest.mark.skipif(importlib.util.find_spec("fastapi") is None, reason="fastapi extra not installed")
@pytest.mark.skipif(importlib.util.find_spec("mangum") is None, reason="mangum extra not installed")
def test_fastapi_builder_autoloads_container_and_resolves_route_dependency(tmp_path: Path) -> None:
    from fastapi import APIRouter, FastAPI
    from fastapi.testclient import TestClient

    from iisi_app_core.integrations.fastapi import FastApiMangumBuilder, from_container

    pkg_root = tmp_path / "samplepkg_fastapi"
    _create_pkg_file(pkg_root / "__init__.py")
    _create_pkg_file(
        pkg_root / "ports.py",
        """
from iisi_app_core import driven_port, driving_port


@driven_port
class UserRepository:
    def get(self, user_id: str) -> dict[str, str]:
        raise NotImplementedError


@driving_port
class GetUserProfilePort:
    def get_user_profile(self, user_id: str) -> dict[str, str]:
        raise NotImplementedError
""",
    )
    _create_pkg_file(
        pkg_root / "adapters.py",
        """
from iisi_app_core import driven_adapter
from .ports import UserRepository


@driven_adapter(port=UserRepository)
class DynamoUserRepository(UserRepository):
    def get(self, user_id: str) -> dict[str, str]:
        return {"id": user_id, "name": "Ada"}
""",
    )
    _create_pkg_file(
        pkg_root / "application.py",
        """
from iisi_app_core import application
from .ports import GetUserProfilePort, UserRepository


@application(port=GetUserProfilePort)
class GetUserProfile(GetUserProfilePort):
    def __init__(self, repo: UserRepository) -> None:
        self.repo = repo

    def get_user_profile(self, user_id: str) -> dict[str, str]:
        return self.repo.get(user_id)
""",
    )

    sys.path.insert(0, str(tmp_path))
    try:
        package = importlib.import_module("samplepkg_fastapi")
        ports_module = importlib.import_module("samplepkg_fastapi.ports")

        def create_app(_) -> FastAPI:
            router = APIRouter()

            @router.get("/users/{user_id}")
            def get_user_profile(
                user_id: str,
                use_case: Annotated[
                    ports_module.GetUserProfilePort,
                    from_container(ports_module.GetUserProfilePort),
                ],
            ) -> dict[str, str]:
                return use_case.get_user_profile(user_id)

            app = FastAPI()
            app.include_router(router)
            return app

        built = FastApiMangumBuilder().autoload(package).app(create_app).build()
        client = TestClient(built.app)
        response = client.get("/users/123")
    finally:
        sys.path.remove(str(tmp_path))

    assert response.status_code == 200
    assert response.json() == {"id": "123", "name": "Ada"}
    assert built.handler is None
    assert built.app.state.container is built.container
    assert built.app.state.registry is built.registry
    assert [module.__name__ for module in built.modules] == [
        "samplepkg_fastapi.adapters",
        "samplepkg_fastapi.application",
        "samplepkg_fastapi.ports",
    ]


@pytest.mark.skipif(importlib.util.find_spec("fastapi") is None, reason="fastapi extra not installed")
@pytest.mark.skipif(importlib.util.find_spec("mangum") is None, reason="mangum extra not installed")
def test_fastapi_builder_passes_manual_bindings_through_to_routes(tmp_path: Path) -> None:
    from fastapi import APIRouter, FastAPI
    from fastapi.testclient import TestClient

    from iisi_app_core.integrations.fastapi import FastApiMangumBuilder, from_container

    pkg_root = tmp_path / "samplepkg_fastapi_manuals"
    _create_pkg_file(pkg_root / "__init__.py")
    _create_pkg_file(
        pkg_root / "ports.py",
        """
from iisi_app_core import driven_port, driving_port


@driven_port
class UserRepository:
    def get(self, user_id: str) -> dict[str, str]:
        raise NotImplementedError


@driving_port
class GetUserProfilePort:
    def get_user_profile(self, user_id: str) -> dict[str, str]:
        raise NotImplementedError
""",
    )
    _create_pkg_file(
        pkg_root / "adapters.py",
        """
from iisi_app_core import driven_adapter
from .ports import UserRepository


class Settings:
    def __init__(self, prefix: str) -> None:
        self.prefix = prefix


class Clock:
    def now(self) -> str:
        return "2026-03-07T12:00:00Z"


@driven_adapter(port=UserRepository)
class DynamoUserRepository(UserRepository):
    def __init__(self, settings: Settings, clock: Clock) -> None:
        self.settings = settings
        self.clock = clock

    def get(self, user_id: str) -> dict[str, str]:
        return {
            "id": user_id,
            "name": f"{self.settings.prefix}-{user_id}",
            "time": self.clock.now(),
        }
""",
    )
    _create_pkg_file(
        pkg_root / "application.py",
        """
from iisi_app_core import application
from .ports import GetUserProfilePort, UserRepository


@application(port=GetUserProfilePort)
class GetUserProfile(GetUserProfilePort):
    def __init__(self, repo: UserRepository) -> None:
        self.repo = repo

    def get_user_profile(self, user_id: str) -> dict[str, str]:
        return self.repo.get(user_id)
""",
    )

    sys.path.insert(0, str(tmp_path))
    try:
        package = importlib.import_module("samplepkg_fastapi_manuals")
        ports_module = importlib.import_module("samplepkg_fastapi_manuals.ports")
        adapters_module = importlib.import_module("samplepkg_fastapi_manuals.adapters")

        def create_app(_) -> FastAPI:
            router = APIRouter()

            @router.get("/users/{user_id}")
            def get_user_profile(
                user_id: str,
                use_case: Annotated[
                    ports_module.GetUserProfilePort,
                    from_container(ports_module.GetUserProfilePort),
                ],
            ) -> dict[str, str]:
                return use_case.get_user_profile(user_id)

            app = FastAPI()
            app.include_router(router)
            return app

        built = (
            FastApiMangumBuilder()
            .autoload(package)
            .add_instance(adapters_module.Settings, adapters_module.Settings("cfg"))
            .add_factory(adapters_module.Clock, adapters_module.Clock, lifetime=ComponentLifetime.SINGLETON)
            .app(create_app)
            .build()
        )
        client = TestClient(built.app)
        response = client.get("/users/123")
    finally:
        sys.path.remove(str(tmp_path))

    assert response.status_code == 200
    assert response.json() == {"id": "123", "name": "cfg-123", "time": "2026-03-07T12:00:00Z"}


@pytest.mark.skipif(importlib.util.find_spec("fastapi") is None, reason="fastapi extra not installed")
@pytest.mark.skipif(importlib.util.find_spec("mangum") is None, reason="mangum extra not installed")
def test_fastapi_builder_can_build_mangum_handler(tmp_path: Path) -> None:
    from fastapi import FastAPI

    from iisi_app_core.integrations.fastapi import FastApiMangumBuilder

    pkg_root = tmp_path / "samplepkg_fastapi_mangum"
    _create_pkg_file(pkg_root / "__init__.py")
    _create_pkg_file(
        pkg_root / "components.py",
        """
from iisi_app_core import driving_port, application


@driving_port
class PingPort:
    def ping(self) -> str:
        raise NotImplementedError


@application(port=PingPort)
class Ping(PingPort):
    def ping(self) -> str:
        return "pong"
""",
    )

    sys.path.insert(0, str(tmp_path))
    try:
        package = importlib.import_module("samplepkg_fastapi_mangum")

        built = (
            FastApiMangumBuilder()
            .autoload(package)
            .app(lambda _: FastAPI())
            .enable_mangum(lifespan="off")
            .build()
        )
    finally:
        sys.path.remove(str(tmp_path))

    assert built.handler is not None
    assert getattr(built.handler, "app") is built.app


@pytest.mark.skipif(importlib.util.find_spec("fastapi") is None, reason="fastapi extra not installed")
@pytest.mark.skipif(importlib.util.find_spec("mangum") is None, reason="mangum extra not installed")
def test_fastapi_builder_preserves_ports_and_adapters_validation(tmp_path: Path) -> None:
    from fastapi import FastAPI

    from iisi_app_core.integrations.fastapi import FastApiMangumBuilder

    pkg_root = tmp_path / "samplepkg_fastapi_invalid"
    _create_pkg_file(pkg_root / "__init__.py")
    _create_pkg_file(
        pkg_root / "components.py",
        """
from iisi_app_core import application, driven_adapter, driven_port, driving_port


@driven_port
class UserRepository:
    def get(self, user_id: str) -> dict[str, str]:
        raise NotImplementedError


@driven_adapter(port=UserRepository)
class DynamoUserRepository(UserRepository):
    def get(self, user_id: str) -> dict[str, str]:
        return {"id": user_id}


@driving_port
class GetUserProfilePort:
    def get_user_profile(self, user_id: str) -> dict[str, str]:
        raise NotImplementedError


@application(port=GetUserProfilePort)
class GetUserProfile(GetUserProfilePort):
    def __init__(self, repo: DynamoUserRepository) -> None:
        self.repo = repo

    def get_user_profile(self, user_id: str) -> dict[str, str]:
        return self.repo.get(user_id)
""",
    )

    sys.path.insert(0, str(tmp_path))
    try:
        package = importlib.import_module("samplepkg_fastapi_invalid")

        with pytest.raises(PortsAndAdaptersViolationError, match="decorated implementation '.*DynamoUserRepository'"):
            FastApiMangumBuilder().autoload(package).app(lambda _: FastAPI()).build()
    finally:
        sys.path.remove(str(tmp_path))
