"""
Django Test Framework Integration.

Custom test runner that spins up PostgreSQL (pgvector) and Neo4j containers.
"""

import logging
import os
from typing import Any, Dict, List

from django.test.runner import DiscoverRunner

from .config import TestDatabaseConfig, TestNeo4jConfig
from .manager import DockerNeo4jManager, DockerPostgreSQLManager

logger = logging.getLogger(__name__)


class PostgreSQLTestRunner(DiscoverRunner):
    """
    Custom Django test runner using Docker PostgreSQL + Neo4j containers.

    Usage in settings.py or pytest.ini:
        TEST_RUNNER = "test_db_manager.django_integration.PostgreSQLTestRunner"
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.pg_manager: DockerPostgreSQLManager | None = None
        self.neo4j_manager: DockerNeo4jManager | None = None
        self._original_databases: Dict[str, Dict[str, Any]] = {}

    def setup_databases(self, **kwargs) -> List[tuple]:
        """Spin up containers, swap DATABASES, then delegate to Django."""
        from django.conf import settings

        self._original_databases = dict(settings.DATABASES)

        # ── PostgreSQL ─────────────────────────────────────────────────
        pg_config = TestDatabaseConfig(
            name=os.environ.get("TEST_DB_NAME", "mnemosyne"),
            memory_limit=os.environ.get("TEST_DB_MEMORY", "512m"),
            port=int(os.environ.get("TEST_DB_PORT", "0")),
        )
        self.pg_manager = DockerPostgreSQLManager(pg_config)
        self.pg_manager.create_container()

        if not self.pg_manager.wait_for_ready(timeout=pg_config.connection_timeout):
            self.pg_manager.cleanup()
            raise RuntimeError("PostgreSQL test container failed to start")

        from django.db import connections

        db_cfg = self.pg_manager.get_django_database_config()
        # Preserve Django's defaulted top-level keys (ATOMIC_REQUESTS,
        # AUTOCOMMIT, OPTIONS, …) and the TEST sub-dict (CHARSET, MIRROR,
        # MIGRATE, …) — these are populated lazily by Django and absent from
        # the user's raw settings.DATABASES, so a naive overwrite breaks
        # request handling that consults them.
        existing = connections["default"].settings_dict
        existing_test = existing.get("TEST", {})
        merged = {**existing, **db_cfg}
        merged["TEST"] = {**existing_test, **db_cfg.get("TEST", {})}
        settings.DATABASES["default"] = merged
        # The default connection was instantiated at Django bootstrap; its
        # settings_dict is independent of settings.DATABASES. Sync it
        # manually so test code talks to the container, not the dev DB.
        connections["default"].settings_dict.update(merged)
        logger.info("PostgreSQL test DB ready on port %s", self.pg_manager.assigned_port)

        # ── Neo4j ──────────────────────────────────────────────────────
        neo4j_enabled = os.environ.get("TEST_NEO4J_ENABLED", "1") == "1"
        if neo4j_enabled:
            neo4j_config = TestNeo4jConfig(
                name=os.environ.get("TEST_NEO4J_NAME", "mnemosyne"),
                memory_limit=os.environ.get("TEST_NEO4J_MEMORY", "1g"),
                port_bolt=int(os.environ.get("TEST_NEO4J_BOLT_PORT", "0")),
                port_http=int(os.environ.get("TEST_NEO4J_HTTP_PORT", "0")),
            )
            self.neo4j_manager = DockerNeo4jManager(neo4j_config)
            self.neo4j_manager.create_container()

            if not self.neo4j_manager.wait_for_ready(timeout=neo4j_config.connection_timeout):
                self.neo4j_manager.cleanup()
                self.pg_manager.cleanup()
                raise RuntimeError("Neo4j test container failed to start")

            # Configure neomodel to use the test container
            bolt_url = self.neo4j_manager.get_bolt_url()
            settings.NEOMODEL_NEO4J_BOLT_URL = bolt_url
            os.environ["NEO4J_BOLT_URL"] = bolt_url

            try:
                from neomodel import config as neo_config

                neo_config.DATABASE_URL = bolt_url
            except ImportError:
                pass

            logger.info("Neo4j test DB ready on bolt port %s", self.neo4j_manager.assigned_bolt_port)

        # Containers were just created — DB already exists, so flip keepdb to
        # skip "CREATE DATABASE" (which would fail; test_user is not superuser).
        # Django still runs migrations to populate the schema.
        self.keepdb = True
        return super().setup_databases(**kwargs)

    def teardown_databases(self, old_config, **kwargs) -> None:
        """Tear down databases and clean up containers."""
        super().teardown_databases(old_config, **kwargs)

        from django.conf import settings

        if self.neo4j_manager:
            self.neo4j_manager.cleanup()
        if self.pg_manager:
            self.pg_manager.cleanup()

        # Restore original database config
        settings.DATABASES = dict(self._original_databases)


def get_postgresql_test_runner():
    """Factory function returning the test runner class."""
    return PostgreSQLTestRunner
