"""
Smart Test Runner for django-cfg.

Automatically:
- Removes conflicting test databases without confirmation
- Installs PostgreSQL extensions (pgvector, pg_trgm, etc.)
- Validates migrations
- Zero configuration - works out of the box

🔥 Generated with django-cfg
"""

import sys
from typing import Any, Dict, Optional

from django.conf import settings
from django.db import connections
from django.test.runner import DiscoverRunner

from ..management.utils.postgresql import PostgreSQLExtensionManager
from ..management.utils.migration_manager import MigrationManager


class SmartTestRunner(DiscoverRunner):
    """
    Smart test runner for django-cfg.

    AUTOMATICALLY:
    - Removes conflicting test databases (fixes EOFError in CI)
    - Installs PostgreSQL extensions (pgvector, pg_trgm, unaccent)
    - Validates migrations

    User doesn't need to configure anything!

    Usage:
        Automatically set in settings.py via django-cfg:
        TEST_RUNNER = 'django_cfg.testing.runners.SmartTestRunner'

        Or manually:
        python manage.py test --testrunner=django_cfg.testing.runners.SmartTestRunner
    """

    def setup_databases(self, **kwargs) -> list:
        """
        Override for automatic extension installation.

        Args:
            **kwargs: Arguments for standard setup_databases

        Returns:
            Old database configuration for teardown
        """

        # 🔥 STEP 1: Remove old test databases (if conflicts exist)
        self._cleanup_old_test_databases()

        # STEP 2: Use smart migration manager for test database setup
        # This automatically handles:
        # - Migration consistency issues
        # - Extension installation
        # - Dependency order problems
        # - StateApps validation errors (swappable dependencies)
        from django.db.migrations import loader as migrations_loader
        from django.db.migrations import state as migrations_state

        original_check = migrations_loader.MigrationLoader.check_consistent_history
        original_state_apps_init = migrations_state.StateApps.__init__

        def patched_check(self, connection):
            """Skip consistency check and use smart migration manager instead."""
            try:
                return original_check(self, connection)
            except Exception as e:
                if 'InconsistentMigrationHistory' in str(type(e).__name__):
                    sys.stderr.write(f"⚠️  Using smart migration manager to fix: {e}\n")
                    return  # Skip check, will be handled by migration manager
                raise

        def patched_state_apps_init(self, real_apps, models, ignore_swappable=False):
            """Bypass StateApps lazy-reference validation during test DB creation.

            Django's StateApps.__init__ calls _check_lazy_references which validates
            that all FK targets are installed. During test DB creation, migration state
            is rebuilt from scratch and some apps may not be resolved yet, causing
            spurious "isn't installed" errors for otherwise-valid FK references.
            Safe to suppress: the real DB startup already validated these references.
            """
            from django.core.checks.model_checks import _check_lazy_references
            from unittest.mock import patch as _mock_patch
            with _mock_patch(
                "django.core.checks.model_checks._check_lazy_references",
                side_effect=lambda apps, ignore=None: [],
            ):
                try:
                    original_state_apps_init(self, real_apps, models, ignore_swappable)
                except ValueError as e:
                    if "isn't installed" in str(e):
                        sys.stderr.write(f"⚠️  Bypassing StateApps lazy-ref validation: {e}\n")
                    else:
                        raise

        migrations_loader.MigrationLoader.check_consistent_history = patched_check
        migrations_state.StateApps.__init__ = patched_state_apps_init

        try:
            old_config = super().setup_databases(**kwargs)
        finally:
            # Restore original methods
            migrations_loader.MigrationLoader.check_consistent_history = original_check
            migrations_state.StateApps.__init__ = original_state_apps_init

        # 🔥 STEP 3: Install extensions AFTER database creation
        self._install_extensions()

        return old_config

    def _cleanup_old_test_databases(self):
        """
        Automatic removal of conflicting test databases.

        Solves problems:
        - EOFError when trying input() in CI
        - Inconsistent migration history in old database
        """
        for alias in connections:
            connection = connections[alias]
            db_engine = connection.settings_dict.get('ENGINE', '')

            # Only work with PostgreSQL
            if 'postgresql' not in db_engine.lower():
                continue

            test_db_name = self._get_test_db_name(connection)

            try:
                # Check if test database exists
                if self._test_database_exists(connection, test_db_name):
                    # 🔥 Automatically remove without confirmation
                    self._drop_test_database(connection, test_db_name)

            except Exception as e:
                # Ignore errors - database may not exist or be unavailable
                if self.verbosity >= 2:
                    self.log(f"⚠️  Could not check/cleanup test database {test_db_name}: {e}")

    def _test_database_exists(self, connection, test_db_name: str) -> bool:
        """
        Check if test database exists.

        Args:
            connection: Django database connection
            test_db_name: Test database name

        Returns:
            True if database exists
        """
        try:
            # Connect to main database for checking
            original_db = connection.settings_dict['NAME']
            connection.settings_dict['NAME'] = 'postgres'  # Default maintenance DB
            connection.close()

            with connection.cursor() as cursor:
                cursor.execute(
                    "SELECT 1 FROM pg_database WHERE datname = %s",
                    [test_db_name]
                )
                exists = cursor.fetchone() is not None

            # Restore original database name
            connection.settings_dict['NAME'] = original_db
            connection.close()

            return exists

        except Exception:
            return False

    def _drop_test_database(self, connection, test_db_name: str):
        """
        Drop test database.

        Args:
            connection: Django database connection
            test_db_name: Test database name
        """
        try:
            # Connect to postgres database for deletion
            original_db = connection.settings_dict['NAME']
            connection.settings_dict['NAME'] = 'postgres'
            connection.close()

            with connection.cursor() as cursor:
                # Terminate all connections to test database
                cursor.execute("""
                    SELECT pg_terminate_backend(pg_stat_activity.pid)
                    FROM pg_stat_activity
                    WHERE pg_stat_activity.datname = %s
                    AND pid <> pg_backend_pid()
                """, [test_db_name])

                # Drop database
                cursor.execute(f'DROP DATABASE IF EXISTS "{test_db_name}"')

            # Restore original database name
            connection.settings_dict['NAME'] = original_db
            connection.close()

            if self.verbosity >= 1:
                self.log(f"✅ Removed old test database: {test_db_name}")

        except Exception as e:
            if self.verbosity >= 2:
                self.log(f"⚠️  Could not remove old test database {test_db_name}: {e}")

    def _install_extensions(self):
        """
        Automatic installation of PostgreSQL extensions in test database.

        Solves problems:
        - Missing pgvector extension
        - Type 'vector' does not exist errors
        """
        for alias in connections:
            connection = connections[alias]
            db_engine = connection.settings_dict.get('ENGINE', '')

            # Only work with PostgreSQL
            if 'postgresql' not in db_engine.lower():
                continue

            # Use existing PostgreSQLExtensionManager
            manager = PostgreSQLExtensionManager()

            try:
                # 🔥 Check if extensions are needed
                needs_pgvector = manager.check_if_pgvector_needed()

                if needs_pgvector:
                    # Install extensions
                    with connection.cursor() as cursor:
                        cursor.execute("CREATE EXTENSION IF NOT EXISTS vector;")
                        cursor.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;")
                        cursor.execute("CREATE EXTENSION IF NOT EXISTS unaccent;")

                    if self.verbosity >= 1:
                        self.log(f"✅ Installed PostgreSQL extensions for test database '{alias}'")

            except Exception as e:
                # Log error but don't fail - extensions may not be needed
                if self.verbosity >= 2:
                    self.log(f"⚠️  Could not install extensions for {alias}: {e}")

    def _get_test_db_name(self, connection) -> str:
        """
        Get test database name from configuration.

        Args:
            connection: Django database connection

        Returns:
            Test database name
        """
        test_db_name = connection.settings_dict.get('TEST', {}).get('NAME')
        if not test_db_name:
            db_name = connection.settings_dict['NAME']
            test_db_name = f'test_{db_name}'
        return test_db_name

    def log(self, message: str, level=None):
        """
        Log messages.

        Args:
            message: Message to output
            level: Log level (optional, for Django compatibility)
        """
        if self.verbosity >= 1:
            # Use sys.stderr to not interfere with test output
            sys.stderr.write(f"{message}\n")


class FastTestRunner(SmartTestRunner):
    """
    Fast test runner using SQLite in-memory.

    Automatically switches all databases to SQLite to speed up unit tests.
    Use for fast tests without complex database features.

    Usage:
        python manage.py test --testrunner=django_cfg.testing.runners.FastTestRunner
    """

    def setup_databases(self, **kwargs) -> list:
        """Override to switch to SQLite."""

        # Switch all databases to SQLite in-memory
        self._switch_to_sqlite()

        # Call parent setup (without cleanup, as SQLite)
        return DiscoverRunner.setup_databases(self, **kwargs)

    def _switch_to_sqlite(self):
        """Switch all databases to SQLite in-memory for speed."""
        for alias in connections:
            connection = connections[alias]

            # Save original configuration
            if not hasattr(connection, '_original_settings'):
                connection._original_settings = connection.settings_dict.copy()

            # Switch to SQLite
            connection.settings_dict.update({
                'ENGINE': 'django.db.backends.sqlite3',
                'NAME': ':memory:',
            })

            # Reopen connection
            connection.close()

        if self.verbosity >= 1:
            self.log("🚀 Switched to SQLite in-memory for fast testing")


__all__ = [
    'SmartTestRunner',
    'FastTestRunner',
]
