from __future__ import annotations

import threading

from sqlalchemy import inspect, text
from sqlalchemy.orm import Session

from app.core.config import get_settings
from app.core.logging import get_logger
from app.db.schema_ownership import create_legacy_schema
from app.db.session import get_session_factory
from app.services.agent_foundation_asset_helpers import AgentFoundationAssetHelperMixin
from app.services.agent_foundation_asset_seed import AgentFoundationAssetSeedMixin
from app.services.agent_foundation_asset_topup import AgentFoundationAssetTopUpMixin
from app.services.agent_foundation_digital_employee_tasks import (
    AgentFoundationDigitalEmployeeTaskMixin,
)
from app.services.agent_foundation_financial_seed import AgentFoundationFinancialSeedMixin
from app.services.agent_foundation_markdown import AgentFoundationMarkdownMixin
from app.services.agent_foundation_risk_rules import AgentFoundationRiskRuleMixin
from app.services.agent_foundation_spreadsheets import AgentFoundationSpreadsheetMixin
from app.services.tenant_registry import TenantRegistryService

logger = get_logger("app.services.agent_foundation")
_foundation_ready_lock = threading.RLock()
_foundation_ready_keys: set[str] = set()


def prepare_agent_foundation() -> None:
    settings = get_settings()
    if not settings.setup_completed:
        logger.info("Agent foundation bootstrap skipped because setup is incomplete")
        return

    session_factory = get_session_factory()
    with session_factory() as db:
        AgentFoundationService(db).ensure_foundation_ready()


class AgentFoundationService(
    AgentFoundationAssetSeedMixin,
    AgentFoundationFinancialSeedMixin,
    AgentFoundationDigitalEmployeeTaskMixin,
    AgentFoundationAssetTopUpMixin,
    AgentFoundationSpreadsheetMixin,
    AgentFoundationAssetHelperMixin,
    AgentFoundationMarkdownMixin,
    AgentFoundationRiskRuleMixin,
):
    def __init__(self, db: Session) -> None:
        self.db = db

    def ensure_foundation_ready(self) -> None:
        cache_key = self._foundation_cache_key()
        if cache_key in _foundation_ready_keys:
            return

        with _foundation_ready_lock:
            if cache_key in _foundation_ready_keys:
                return
            self._prepare_foundation()
            _foundation_ready_keys.add(cache_key)

    def _prepare_foundation(self) -> None:
        try:
            create_legacy_schema(self.db.get_bind())
            TenantRegistryService(self.db).ensure_builtin()
            self._ensure_agent_asset_schema()
            self._ensure_financial_record_schema()
            self._seed_agent_assets()
            self._sync_demo_financial_records()
            self._seed_runs_and_logs()
            self.db.commit()
        except Exception:
            self.db.rollback()
            logger.exception("Failed to prepare agent foundation")
            raise

    def _foundation_cache_key(self) -> str:
        bind = self.db.get_bind()
        url = str(getattr(bind, "url", "") or "")
        if url.endswith("/:memory:"):
            return f"{url}:{id(bind)}"
        return url or str(id(bind))

    def _ensure_financial_record_schema(self) -> None:
        bind = self.db.get_bind()
        inspector = inspect(bind)
        if "expense_claims" not in inspector.get_table_names():
            return

        column_names = {column["name"] for column in inspector.get_columns("expense_claims")}
        dialect_name = bind.dialect.name
        timestamp_type = "TIMESTAMP WITH TIME ZONE" if dialect_name == "postgresql" else "DATETIME"
        boolean_default = "FALSE" if dialect_name == "postgresql" else "0"

        if "hermes_scanned_at" not in column_names:
            self.db.execute(
                text(f"ALTER TABLE expense_claims ADD COLUMN hermes_scanned_at {timestamp_type}")
            )
        if "hermes_risk_flag" not in column_names:
            self.db.execute(
                text(
                    "ALTER TABLE expense_claims "
                    f"ADD COLUMN hermes_risk_flag BOOLEAN DEFAULT {boolean_default} NOT NULL"
                )
            )
            self.db.execute(
                text(
                    "CREATE INDEX IF NOT EXISTS ix_expense_claims_hermes_risk_flag "
                    "ON expense_claims (hermes_risk_flag)"
                )
            )
        if "expense_claim_items" in inspector.get_table_names():
            item_column_names = {column["name"] for column in inspector.get_columns("expense_claim_items")}
            if "item_note" not in item_column_names:
                self.db.execute(
                    text("ALTER TABLE expense_claim_items ADD COLUMN item_note TEXT DEFAULT '' NOT NULL")
                )
        self.db.flush()

    def _sync_demo_financial_records(self) -> None:
        if get_settings().seed_demo_financial_records:
            self._seed_financial_records()
            return
        self._purge_demo_financial_records()
