"""
Local bootstrap service for single-tenant mode.

Handles automatic tenant creation when LOCAL_MODE is enabled.
Designed to be migration-safe: gracefully handles cases where
database tables don't exist yet.
"""

import asyncio
import logging
from typing import Optional
from uuid import UUID

from sqlalchemy import select, text
from sqlalchemy.exc import OperationalError, ProgrammingError

from app.config import settings
from app.db import async_session_maker
from app.models import Tenant

logger = logging.getLogger(__name__)


class LocalBootstrapService:
    """
    Service for bootstrapping local single-tenant mode.

    When LOCAL_MODE=true:
    - Waits for database migrations to complete
    - Creates or retrieves the local tenant
    - Makes tenant_id available for the meta endpoint

    When LOCAL_MODE=false:
    - Does nothing (multi-tenant mode unchanged)
    """

    # Class-level state for meta endpoint access
    _local_tenant_id: Optional[UUID] = None
    _bootstrap_attempted: bool = False
    _bootstrap_error: Optional[str] = None

    # Bootstrap configuration
    MAX_RETRIES = 30  # Max attempts waiting for migrations
    RETRY_DELAY_SECONDS = 2  # Delay between retries
    LOCAL_TENANT_NAME = "local"

    @classmethod
    def get_local_tenant_id(cls) -> Optional[UUID]:
        """Get the local tenant ID if bootstrap succeeded."""
        return cls._local_tenant_id

    @classmethod
    def get_bootstrap_status(cls) -> dict:
        """Get bootstrap status for diagnostics."""
        return {
            "attempted": cls._bootstrap_attempted,
            "success": cls._local_tenant_id is not None,
            "tenant_id": str(cls._local_tenant_id) if cls._local_tenant_id else None,
            "error": cls._bootstrap_error,
        }

    @classmethod
    async def run(cls) -> None:
        """
        Run the bootstrap process.

        Only executes if LOCAL_MODE is enabled.
        Safe to call multiple times (idempotent).
        """
        if not settings.LOCAL_MODE:
            logger.debug("LOCAL_MODE is disabled, skipping bootstrap")
            return

        if cls._bootstrap_attempted:
            logger.debug("Bootstrap already attempted, skipping")
            return

        cls._bootstrap_attempted = True
        logger.info("LOCAL_MODE enabled, starting local tenant bootstrap")

        # Validate required settings
        if not settings.INSTANCE_ID:
            cls._bootstrap_error = "INSTANCE_ID is required when LOCAL_MODE is enabled"
            logger.error(cls._bootstrap_error)
            return

        try:
            await cls._bootstrap_with_retry()
        except Exception as e:
            cls._bootstrap_error = str(e)
            logger.exception("Bootstrap failed with unexpected error")

    @classmethod
    async def _bootstrap_with_retry(cls) -> None:
        """
        Attempt bootstrap with retry logic for migration safety.

        Waits for the tenants table to exist before proceeding.
        """
        for attempt in range(1, cls.MAX_RETRIES + 1):
            try:
                await cls._ensure_local_tenant()
                logger.info(f"Local tenant bootstrap succeeded (tenant_id={cls._local_tenant_id})")
                return
            except (OperationalError, ProgrammingError) as e:
                # These errors typically indicate migrations haven't run yet
                error_msg = str(e).lower()
                if "does not exist" in error_msg or "no such table" in error_msg:
                    if attempt < cls.MAX_RETRIES:
                        logger.warning(
                            f"Database table not ready (attempt {attempt}/{cls.MAX_RETRIES}), "
                            f"retrying in {cls.RETRY_DELAY_SECONDS}s..."
                        )
                        await asyncio.sleep(cls.RETRY_DELAY_SECONDS)
                        continue
                    else:
                        cls._bootstrap_error = f"Migrations did not complete after {cls.MAX_RETRIES} attempts"
                        logger.error(cls._bootstrap_error)
                        return
                else:
                    # Unexpected database error
                    raise

    @classmethod
    async def _ensure_local_tenant(cls) -> None:
        """
        Ensure the local tenant exists.

        Creates it if missing, retrieves it if already exists.
        """
        async with async_session_maker() as session:
            # First, verify we can query the tenants table
            # This will fail fast if migrations haven't run
            await session.execute(text("SELECT 1 FROM tenants LIMIT 1"))

            # Check if local tenant exists
            result = await session.execute(
                select(Tenant).where(Tenant.name == cls.LOCAL_TENANT_NAME)
            )
            tenant = result.scalar_one_or_none()

            if tenant:
                logger.info(f"Local tenant already exists (id={tenant.id})")
                cls._local_tenant_id = tenant.id
                return

            # Create local tenant
            tenant = Tenant(
                name=cls.LOCAL_TENANT_NAME,
                domain=settings.LOCAL_TENANT_DOMAIN,
            )
            session.add(tenant)
            await session.commit()
            await session.refresh(tenant)

            cls._local_tenant_id = tenant.id
            logger.info(f"Created local tenant (id={tenant.id}, domain={settings.LOCAL_TENANT_DOMAIN})")

    @classmethod
    async def _check_table_exists(cls, table_name: str) -> bool:
        """Check if a database table exists."""
        async with async_session_maker() as session:
            try:
                await session.execute(text(f"SELECT 1 FROM {table_name} LIMIT 1"))
                return True
            except (OperationalError, ProgrammingError):
                return False
