"""
Startup validation to ensure system consistency before running.
This prevents runtime surprises from configuration or schema mismatches.
"""

import logging
import os
import sys
from typing import List, Tuple

from sqlalchemy import create_engine, inspect
from sqlalchemy.engine import make_url

from .settings import DATABASE_URL

logger = logging.getLogger(__name__)


class StartupValidator:
    """Validates system state at startup"""

    def __init__(self) -> None:
        self.errors: List[str] = []
        self.warnings: List[str] = []

    def validate_encryption_config(self) -> None:
        """Ensure encryption is properly configured"""
        if not os.getenv("ENCRYPTION_KEY"):
            self.errors.append(
                "ENCRYPTION_KEY not set. API key encryption will fail. Set this in your .env file."
            )

    def validate_internal_api_key_config(self) -> None:
        """Ensure internal webhook endpoints are protected in production."""
        env = (os.getenv("ENV", "development") or "").strip().lower()
        if env == "production" and not os.getenv("INTERNAL_API_KEY"):
            self.errors.append(
                "INTERNAL_API_KEY not set. Internal webhook endpoints (seat sync/activation) "
                "will be unavailable in production. Set INTERNAL_API_KEY in your environment."
            )

    def _sync_database_url(self) -> str:
        url = make_url(DATABASE_URL)
        driver = url.drivername or ""
        if "+asyncpg" in driver:
            url = url.set(drivername=driver.replace("+asyncpg", ""))
        if "+aiosqlite" in driver:
            url = url.set(drivername=driver.replace("+aiosqlite", ""))
        # NOTE: str(url) hides the password (renders as ***). We need the full DSN
        # when passing it back into SQLAlchemy, otherwise psycopg2 sees "***".
        return url.render_as_string(hide_password=False)

    def validate_database_schema(self) -> None:
        """Check that database schema matches expectations"""
        try:
            sync_url = self._sync_database_url()
            engine = create_engine(sync_url)
            inspector = inspect(engine)

            safe_url = make_url(sync_url).render_as_string(hide_password=True)
            logger.debug("Database URL resolved to: %s", safe_url)

            # Check critical tables exist
            required_tables = ["api_keys", "accounts", "users", "alembic_version"]
            existing_tables = inspector.get_table_names()
            logger.debug("Existing tables: %s", existing_tables)

            for table in required_tables:
                if table not in existing_tables:
                    self.errors.append(
                        f"Required table '{table}' is missing from database"
                    )

            # Check api_keys has required columns
            if "api_keys" in existing_tables:
                columns = {col["name"] for col in inspector.get_columns("api_keys")}
                required_columns = {
                    "id",
                    "account_id",
                    "name",
                    "key_prefix",
                    "key_hash",
                    "key_encrypted",
                    "description",
                    "is_active",
                    "created_at",
                }
                missing = required_columns - columns
                if missing:
                    self.errors.append(
                        f"Table 'api_keys' is missing columns: {', '.join(sorted(missing))}"
                    )

            engine.dispose()

        except Exception as exc:  # pylint: disable=broad-except
            self.errors.append(f"Database validation failed: {exc}")

    def validate_redis_connection(self) -> None:
        """Check Redis is accessible"""
        from .settings import REDIS_URL

        if REDIS_URL:
            try:
                import redis

                redis.from_url(REDIS_URL).ping()
            except Exception as exc:  # pylint: disable=broad-except
                self.warnings.append(
                    f"Redis not accessible: {exc}. Background jobs will fail."
                )

    def validate_alembic_state(self) -> None:
        """Check if migrations are up to date"""
        try:
            from alembic import runtime, script
            from alembic.config import Config

            alembic_cfg = Config("alembic.ini")
            script_dir = script.ScriptDirectory.from_config(alembic_cfg)

            sync_url = self._sync_database_url()
            engine = create_engine(sync_url)
            with engine.connect() as conn:
                context = runtime.migration.MigrationContext.configure(conn)
                current_rev = context.get_current_revision()
                head_rev = script_dir.get_current_head()

                if current_rev != head_rev:
                    self.warnings.append(
                        "Database migrations are not up to date. "
                        f"Current: {current_rev}, Latest: {head_rev}. "
                        "Run: alembic upgrade head"
                    )

            engine.dispose()

        except Exception as exc:  # pylint: disable=broad-except
            self.warnings.append(f"Could not check migration status: {exc}")

    def validate_all(self) -> Tuple[List[str], List[str]]:
        """Run all validations and return errors and warnings"""
        if os.getenv("FM_TEST_MODE") == "1":
            logger.info("Skipping startup validation in test mode")
            return self.errors, self.warnings

        logger.info("Running startup validation...")

        self.validate_encryption_config()
        self.validate_internal_api_key_config()
        self.validate_database_schema()
        self.validate_redis_connection()
        self.validate_alembic_state()

        return self.errors, self.warnings

    def run_and_exit_on_error(self) -> None:
        """Run validation and exit if there are critical errors"""
        errors, warnings = self.validate_all()

        for warning in warnings:
            logger.warning("STARTUP WARNING: %s", warning)

        if errors:
            logger.error("=" * 60)
            logger.error("STARTUP VALIDATION FAILED")
            logger.error("=" * 60)
            for error in errors:
                logger.error("  - %s", error)
            logger.error("=" * 60)
            logger.error("Fix the above errors before starting the application.")
            sys.exit(1)

        logger.info("Startup validation passed")


def validate_startup() -> None:
    """Convenience function to run validation"""
    validator = StartupValidator()
    validator.run_and_exit_on_error()
