"""Admin-only router for the universal Fetch Data Toggle system.

Endpoints (all mounted under ``/api/v1/admin/module-sync``):

* ``GET  /modules``                 — list all registered modules with
  their settings, live counters, and last-sync status.
* ``GET  /modules/{module_name}``   — single-module settings+status.
* ``PATCH /modules/{module_name}``  — update one or more toggle flags;
  every change is recorded as an ``AdminAuditLog`` row.
* ``POST /modules/{module_name}/fetch/{sync_type}`` — manual fetch
  action (``clients`` / ``accounts`` / ``domains``), runs with
  ``force=True`` so OFF toggles do not block manual runs.
* ``POST /modules/{module_name}/fetch-all`` — runs all three sync
  types sequentially.
* ``GET  /logs``                    — recent sync log items
  (filterable by module / sync_type).
* ``GET  /logs/{log_id}``           — raw (masked) response for one log.
* ``POST /diagnostics/{module_name}`` — run lightweight checks for all
  three sync types in "force" mode and return a per-step pass/fail.

All endpoints require ``get_admin_user``. Raw responses are already
masked by the engine before being persisted.
"""
from __future__ import annotations

import logging
from datetime import datetime
from typing import List, Optional

from core.database import get_db
from dependencies.auth import get_admin_user
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import and_, desc, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from models.module_sync import ModuleSyncLog, ModuleSyncSettings
from schemas.auth import UserResponse
from schemas.module_sync import (
    ModuleDiagnosticsResult,
    ModuleDiagnosticStep,
    ModuleSyncLogDetail,
    ModuleSyncLogItem,
    ModuleSyncLogList,
    ModuleSyncRunAllResult,
    ModuleSyncRunResult,
    ModuleSyncSettingsOut,
    ModuleSyncToggleUpdate,
)
from services.audit_log import record_admin_event
from services.module_sync import registry
from services.module_sync.engine import (
    SYNC_ACCOUNTS,
    SYNC_CLIENTS,
    SYNC_DOMAINS,
    SYNC_TYPES,
    get_or_create_settings,
    run_all,
    run_sync,
)

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/admin/module-sync", tags=["module-sync"])


# ---------------------------------------------------------------------------
# Serialization helpers
# ---------------------------------------------------------------------------
def _module_label(module_name: str) -> str:
    try:
        return registry.get_adapter(module_name).module_label
    except KeyError:
        return module_name.title()


def _settings_to_out(settings: ModuleSyncSettings) -> ModuleSyncSettingsOut:
    return ModuleSyncSettingsOut(
        module_name=settings.module_name,
        module_label=_module_label(settings.module_name),
        fetch_clients_enabled=bool(settings.fetch_clients_enabled),
        fetch_accounts_enabled=bool(settings.fetch_accounts_enabled),
        fetch_domains_enabled=bool(settings.fetch_domains_enabled),
        last_clients_sync=settings.last_clients_sync,
        last_accounts_sync=settings.last_accounts_sync,
        last_domains_sync=settings.last_domains_sync,
        clients_sync_status=settings.clients_sync_status,
        accounts_sync_status=settings.accounts_sync_status,
        domains_sync_status=settings.domains_sync_status,
        clients_count=int(settings.clients_count or 0),
        accounts_count=int(settings.accounts_count or 0),
        domains_count=int(settings.domains_count or 0),
        domains_linked_count=int(settings.domains_linked_count or 0),
        domains_unlinked_count=int(settings.domains_unlinked_count or 0),
        last_error=settings.last_error,
    )


def _log_to_item(log: ModuleSyncLog) -> ModuleSyncLogItem:
    return ModuleSyncLogItem(
        id=log.id,
        module_name=log.module_name,
        sync_type=log.sync_type,
        status=log.status,
        items_fetched=int(log.items_fetched or 0),
        error_message=log.error_message,
        triggered_by=log.triggered_by,
        actor_email=log.actor_email,
        duration_ms=int(log.duration_ms or 0),
        started_at=log.started_at,
        finished_at=log.finished_at,
    )


# ---------------------------------------------------------------------------
# Module settings endpoints
# ---------------------------------------------------------------------------
@router.get("/modules", response_model=List[ModuleSyncSettingsOut])
async def list_modules(
    current_user: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    """Return settings+counters for every registered module."""
    out: List[ModuleSyncSettingsOut] = []
    for module_name in registry.list_modules():
        settings = await get_or_create_settings(db, module_name)
        out.append(_settings_to_out(settings))
    await db.commit()
    return out


@router.get("/modules/{module_name}", response_model=ModuleSyncSettingsOut)
async def get_module(
    module_name: str,
    current_user: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    _ensure_known_module(module_name)
    settings = await get_or_create_settings(db, module_name)
    await db.commit()
    return _settings_to_out(settings)


@router.patch("/modules/{module_name}", response_model=ModuleSyncSettingsOut)
async def update_toggles(
    module_name: str,
    body: ModuleSyncToggleUpdate,
    current_user: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    """Toggle one or more fetch flags. Every change is audit-logged."""
    _ensure_known_module(module_name)
    settings = await get_or_create_settings(db, module_name)

    changes: dict = {}
    for field_name in ("fetch_clients_enabled", "fetch_accounts_enabled", "fetch_domains_enabled"):
        new_val = getattr(body, field_name)
        if new_val is None:
            continue
        old_val = bool(getattr(settings, field_name))
        if bool(new_val) != old_val:
            setattr(settings, field_name, bool(new_val))
            changes[field_name] = {"from": old_val, "to": bool(new_val)}

    if changes:
        await db.flush()
        await record_admin_event(
            db,
            action="admin.module_sync.toggle_changed",
            admin_user_id=current_user.id,
            admin_email=current_user.email,
            target_user_id=None,
            target_email=None,
            details={"module": module_name, "changes": changes},
        )
    await db.commit()
    await db.refresh(settings)
    return _settings_to_out(settings)


# ---------------------------------------------------------------------------
# Manual fetch endpoints
# ---------------------------------------------------------------------------
@router.post(
    "/modules/{module_name}/fetch/{sync_type}",
    response_model=ModuleSyncRunResult,
)
async def fetch_now(
    module_name: str,
    sync_type: str,
    current_user: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    """Manually trigger one sync type. Ignores the toggle (force=True)."""
    _ensure_known_module(module_name)
    if sync_type not in SYNC_TYPES:
        raise HTTPException(status_code=400, detail=f"Unknown sync_type: {sync_type}")

    started_at = datetime.utcnow()
    attempt = await run_sync(
        db,
        module_name=module_name,
        sync_type=sync_type,
        triggered_by="manual",
        actor_email=current_user.email,
        force=True,
    )
    finished_at = datetime.utcnow()
    duration_ms = int((finished_at - started_at).total_seconds() * 1000)

    await record_admin_event(
        db,
        action="admin.module_sync.manual_fetch",
        admin_user_id=current_user.id,
        admin_email=current_user.email,
        details={
            "module": module_name,
            "sync_type": sync_type,
            "status": attempt.status,
            "items_fetched": attempt.items_fetched,
        },
    )
    await db.commit()

    return ModuleSyncRunResult(
        module_name=module_name,
        sync_type=sync_type,
        status=attempt.status,
        items_fetched=attempt.items_fetched,
        error_message=attempt.error_message,
        started_at=started_at,
        finished_at=finished_at,
        duration_ms=duration_ms,
    )


@router.post("/modules/{module_name}/fetch-all", response_model=ModuleSyncRunAllResult)
async def fetch_all_now(
    module_name: str,
    current_user: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    """Run clients -> accounts -> domains sequentially (force=True)."""
    _ensure_known_module(module_name)
    raw_results = await run_all(
        db,
        module_name=module_name,
        triggered_by="manual",
        actor_email=current_user.email,
        force=True,
    )
    await record_admin_event(
        db,
        action="admin.module_sync.manual_fetch_all",
        admin_user_id=current_user.id,
        admin_email=current_user.email,
        details={"module": module_name, "results": [
            {"sync_type": r["sync_type"], "status": r["status"], "items_fetched": r["items_fetched"]}
            for r in raw_results
        ]},
    )
    await db.commit()
    return ModuleSyncRunAllResult(
        module_name=module_name,
        runs=[ModuleSyncRunResult(**r) for r in raw_results],
    )


# ---------------------------------------------------------------------------
# Logs endpoints
# ---------------------------------------------------------------------------
@router.get("/logs", response_model=ModuleSyncLogList)
async def list_logs(
    module_name: Optional[str] = Query(default=None),
    sync_type: Optional[str] = Query(default=None),
    status_filter: Optional[str] = Query(default=None, alias="status"),
    limit: int = Query(default=100, ge=1, le=500),
    offset: int = Query(default=0, ge=0),
    current_user: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    conds = []
    if module_name:
        conds.append(ModuleSyncLog.module_name == module_name)
    if sync_type:
        conds.append(ModuleSyncLog.sync_type == sync_type)
    if status_filter:
        conds.append(ModuleSyncLog.status == status_filter)
    where = and_(*conds) if conds else None

    stmt = select(ModuleSyncLog)
    if where is not None:
        stmt = stmt.where(where)
    stmt = stmt.order_by(desc(ModuleSyncLog.id)).limit(limit).offset(offset)

    rows = (await db.execute(stmt)).scalars().all()
    count_stmt = select(func.count(ModuleSyncLog.id))
    if where is not None:
        count_stmt = count_stmt.where(where)
    total = int((await db.execute(count_stmt)).scalar() or 0)

    return ModuleSyncLogList(
        items=[_log_to_item(r) for r in rows],
        total=total,
    )


@router.get("/logs/{log_id}", response_model=ModuleSyncLogDetail)
async def get_log(
    log_id: int,
    current_user: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    row = (
        await db.execute(select(ModuleSyncLog).where(ModuleSyncLog.id == log_id))
    ).scalar_one_or_none()
    if row is None:
        raise HTTPException(status_code=404, detail="Sync log not found")
    base = _log_to_item(row)
    return ModuleSyncLogDetail(**base.model_dump(), raw_response=row.raw_response)


# ---------------------------------------------------------------------------
# Diagnostics endpoint
# ---------------------------------------------------------------------------
@router.post("/diagnostics/{module_name}", response_model=ModuleDiagnosticsResult)
async def diagnostics(
    module_name: str,
    current_user: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    """Runs each sync type once in force-mode and reports per-step status."""
    _ensure_known_module(module_name)
    steps: List[ModuleDiagnosticStep] = []
    overall_ok = True

    for st in (SYNC_CLIENTS, SYNC_ACCOUNTS, SYNC_DOMAINS):
        attempt = await run_sync(
            db,
            module_name=module_name,
            sync_type=st,
            triggered_by="diagnostic",
            actor_email=current_user.email,
            force=True,
        )
        ok = attempt.status == "ok"
        overall_ok = overall_ok and ok
        steps.append(
            ModuleDiagnosticStep(
                step=f"fetch_{st}",
                ok=ok,
                detail=(attempt.error_message if not ok else f"items={attempt.items_fetched}"),
            )
        )

    await record_admin_event(
        db,
        action="admin.module_sync.diagnostics_run",
        admin_user_id=current_user.id,
        admin_email=current_user.email,
        details={"module": module_name, "ok": overall_ok},
    )
    await db.commit()
    return ModuleDiagnosticsResult(
        module_name=module_name,
        ok=overall_ok,
        steps=steps,
        message=("All checks passed" if overall_ok else "Some checks failed"),
    )


# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
def _ensure_known_module(module_name: str) -> None:
    try:
        registry.get_adapter(module_name)
    except KeyError:
        raise HTTPException(status_code=404, detail=f"Unknown module: {module_name}")