from __future__ import annotations

from dataclasses import dataclass
from datetime import date, datetime, timedelta, timezone
from typing import Any

from fastapi import Request
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from api.models.legacy_meter import LegacyInterface, LegacyInterfaceUsageBucket
from api.schemas.legacy_meter import (
    LegacyInterfaceRead,
    LegacyInterfaceSummary,
    LegacyUsageCounters,
    LegacyUsageSummary,
    LegacyWeeklyReview,
)

UNKNOWN_BUCKET = "unknown"
VALID_INTERFACE_KINDS = {
    "rest_api",
    "mcp_tool",
    "procedure_call",
    "event_subject",
    "cli",
    "dashboard_route",
    "schema_field",
}
VALID_INTERFACE_STATUSES = {"legacy", "retirement_candidate", "retired"}


@dataclass(frozen=True)
class LegacyUsageIdentity:
    tenant_key: str = UNKNOWN_BUCKET
    user_key: str = UNKNOWN_BUCKET
    component_key: str = UNKNOWN_BUCKET


def identity_from_request(request: Request | None) -> LegacyUsageIdentity:
    if request is None:
        return LegacyUsageIdentity()
    headers = request.headers
    return LegacyUsageIdentity(
        tenant_key=_clean_bucket(headers.get("x-statehub-tenant") or headers.get("x-tenant-id")),
        user_key=_clean_bucket(headers.get("x-statehub-user") or headers.get("x-user-id")),
        component_key=_clean_bucket(headers.get("x-statehub-component") or headers.get("x-component")),
    )


async def register_legacy_interface(
    session: AsyncSession,
    *,
    interface_key: str,
    interface_kind: str,
    replacement_ref: str,
    owner_component: str = "state-hub",
    replacement_verified: bool = False,
    manual_hold: bool = False,
    hold_reason: str | None = None,
    notes: str | None = None,
    status: str = "legacy",
    commit: bool = True,
    preserve_controls: bool = False,
) -> LegacyInterface:
    interface_key = _required(interface_key, "interface_key")
    replacement_ref = _required(replacement_ref, "replacement_ref")
    interface_kind = _validate_kind(interface_kind)
    status = _validate_status(status)

    existing = await _get_by_key(session, interface_key)
    if existing is None:
        existing = LegacyInterface(
            interface_key=interface_key,
            interface_kind=interface_kind,
            replacement_ref=replacement_ref,
            owner_component=_clean_value(owner_component, "state-hub"),
            replacement_verified=replacement_verified,
            manual_hold=manual_hold,
            hold_reason=hold_reason,
            notes=notes,
            status=status,
        )
        session.add(existing)
    else:
        existing.interface_kind = interface_kind
        existing.replacement_ref = replacement_ref
        existing.owner_component = _clean_value(owner_component, "state-hub")
        existing.replacement_verified = existing.replacement_verified or replacement_verified
        if not preserve_controls:
            existing.manual_hold = manual_hold
            existing.hold_reason = hold_reason
            existing.notes = notes if notes is not None else existing.notes
            existing.status = status

    if commit:
        await session.commit()
        await session.refresh(existing)
    else:
        await session.flush()
    return existing


async def patch_legacy_interface(
    session: AsyncSession,
    interface: LegacyInterface,
    updates: dict[str, Any],
) -> LegacyInterface:
    if "interface_kind" in updates:
        updates["interface_kind"] = _validate_kind(updates["interface_kind"])
    if "status" in updates and updates["status"] is not None:
        updates["status"] = _validate_status(updates["status"])
        if updates["status"] == "retired" and interface.retired_at is None:
            interface.retired_at = datetime.now(tz=timezone.utc)
    for field, value in updates.items():
        if value is not None or field in {"manual_hold", "replacement_verified", "hold_reason", "notes"}:
            setattr(interface, field, value)
    await session.commit()
    await session.refresh(interface)
    return interface


async def record_legacy_usage(
    session: AsyncSession,
    *,
    interface_key: str,
    interface_kind: str,
    replacement_ref: str,
    identity: LegacyUsageIdentity | None = None,
    owner_component: str = "state-hub",
    replacement_verified: bool = False,
    observed_at: datetime | None = None,
    call_count: int = 1,
    commit: bool = True,
) -> LegacyInterface:
    if call_count < 1:
        raise ValueError("call_count must be >= 1")
    observed_at = observed_at or datetime.now(tz=timezone.utc)
    if observed_at.tzinfo is None:
        observed_at = observed_at.replace(tzinfo=timezone.utc)
    identity = identity or LegacyUsageIdentity()
    interface = await register_legacy_interface(
        session,
        interface_key=interface_key,
        interface_kind=interface_kind,
        replacement_ref=replacement_ref,
        owner_component=owner_component,
        replacement_verified=replacement_verified,
        preserve_controls=True,
        commit=False,
    )
    for bucket_kind, bucket_key in (
        ("call", "total"),
        ("tenant", identity.tenant_key),
        ("user", identity.user_key),
        ("component", identity.component_key),
    ):
        await _increment_bucket(
            session,
            interface,
            period_start=observed_at.date(),
            bucket_kind=bucket_kind,
            bucket_key=_clean_bucket(bucket_key),
            observed_at=observed_at,
            call_count=call_count,
        )
    if commit:
        await session.commit()
        await session.refresh(interface)
    else:
        await session.flush()
    return interface


async def get_legacy_interface_by_key(
    session: AsyncSession,
    interface_key: str,
) -> LegacyInterface | None:
    return await _get_by_key(session, interface_key)


async def list_legacy_interfaces(session: AsyncSession) -> list[LegacyInterface]:
    result = await session.execute(
        select(LegacyInterface).order_by(LegacyInterface.interface_kind, LegacyInterface.interface_key)
    )
    return list(result.scalars().all())


async def legacy_usage_summary(
    session: AsyncSession,
    *,
    window_start: datetime | None = None,
    window_end: datetime | None = None,
) -> LegacyUsageSummary:
    window_end = _ensure_datetime(window_end) or datetime.now(tz=timezone.utc)
    window_start = _ensure_datetime(window_start) or (window_end - timedelta(days=7))
    interfaces = await list_legacy_interfaces(session)
    buckets = await _usage_buckets(session)
    by_interface: dict[Any, list[LegacyInterfaceUsageBucket]] = {}
    for bucket in buckets:
        by_interface.setdefault(bucket.legacy_interface_id, []).append(bucket)

    summaries = [
        _summarize_interface(interface, by_interface.get(interface.id, []), window_start, window_end)
        for interface in interfaces
    ]
    return LegacyUsageSummary(
        generated_at=datetime.now(tz=timezone.utc),
        window_start=window_start,
        window_end=window_end,
        interfaces=summaries,
    )


async def legacy_weekly_review(
    session: AsyncSession,
    *,
    window_start: datetime | None = None,
    window_end: datetime | None = None,
) -> LegacyWeeklyReview:
    summary = await legacy_usage_summary(
        session,
        window_start=window_start,
        window_end=window_end,
    )
    candidates = [item for item in summary.interfaces if item.retirement_candidate]
    return LegacyWeeklyReview(
        generated_at=summary.generated_at,
        window_start=summary.window_start,
        window_end=summary.window_end,
        activity_core_handoff={
            "activity_id": "statehub-legacy-interface-review",
            "cadence": "weekly",
            "source_endpoint": "/legacy-meter/weekly-review",
            "state_owner": "state-hub",
            "scheduler_owner": "activity-core",
        },
        interfaces=summary.interfaces,
        retirement_candidates=candidates,
    )


async def _increment_bucket(
    session: AsyncSession,
    interface: LegacyInterface,
    *,
    period_start: date,
    bucket_kind: str,
    bucket_key: str,
    observed_at: datetime,
    call_count: int,
) -> LegacyInterfaceUsageBucket:
    result = await session.execute(
        select(LegacyInterfaceUsageBucket).where(
            LegacyInterfaceUsageBucket.legacy_interface_id == interface.id,
            LegacyInterfaceUsageBucket.period_start == period_start,
            LegacyInterfaceUsageBucket.bucket_kind == bucket_kind,
            LegacyInterfaceUsageBucket.bucket_key == bucket_key,
        )
    )
    bucket = result.scalar_one_or_none()
    if bucket is None:
        bucket = LegacyInterfaceUsageBucket(
            legacy_interface_id=interface.id,
            period_start=period_start,
            bucket_kind=bucket_kind,
            bucket_key=bucket_key,
            call_count=call_count,
            first_seen_at=observed_at,
            last_seen_at=observed_at,
        )
        session.add(bucket)
        return bucket

    bucket.call_count += call_count
    if observed_at < bucket.first_seen_at:
        bucket.first_seen_at = observed_at
    if observed_at > bucket.last_seen_at:
        bucket.last_seen_at = observed_at
    return bucket


async def _get_by_key(session: AsyncSession, interface_key: str) -> LegacyInterface | None:
    result = await session.execute(
        select(LegacyInterface).where(LegacyInterface.interface_key == interface_key)
    )
    return result.scalar_one_or_none()


async def _usage_buckets(session: AsyncSession) -> list[LegacyInterfaceUsageBucket]:
    result = await session.execute(select(LegacyInterfaceUsageBucket))
    return list(result.scalars().all())


def _summarize_interface(
    interface: LegacyInterface,
    buckets: list[LegacyInterfaceUsageBucket],
    window_start: datetime,
    window_end: datetime,
) -> LegacyInterfaceSummary:
    all_time = _counters(buckets)
    window_buckets = [
        bucket for bucket in buckets
        if bucket.last_seen_at >= window_start and bucket.first_seen_at < window_end
    ]
    window = _counters(window_buckets)
    last_seen = max((bucket.last_seen_at for bucket in buckets), default=None)
    retirement_candidate, reason = _retirement_state(interface, window.calls)
    return LegacyInterfaceSummary(
        interface=LegacyInterfaceRead.model_validate(interface),
        all_time=all_time,
        window=window,
        last_seen_at=last_seen,
        retirement_candidate=retirement_candidate,
        retirement_reason=reason,
    )


def _counters(buckets: list[LegacyInterfaceUsageBucket]) -> LegacyUsageCounters:
    calls = sum(bucket.call_count for bucket in buckets if bucket.bucket_kind == "call")
    tenants = _bucket_counts(buckets, "tenant")
    users = _bucket_counts(buckets, "user")
    components = _bucket_counts(buckets, "component")
    return LegacyUsageCounters(
        calls=calls,
        tenant_count=len(tenants),
        user_count=len(users),
        component_count=len(components),
        tenants=tenants,
        users=users,
        components=components,
    )


def _bucket_counts(buckets: list[LegacyInterfaceUsageBucket], bucket_kind: str) -> dict[str, int]:
    counts: dict[str, int] = {}
    for bucket in buckets:
        if bucket.bucket_kind == bucket_kind:
            counts[bucket.bucket_key] = counts.get(bucket.bucket_key, 0) + bucket.call_count
    return counts


def _retirement_state(interface: LegacyInterface, window_calls: int) -> tuple[bool, str]:
    if interface.status == "retired":
        return False, "already retired"
    if interface.manual_hold:
        return False, interface.hold_reason or "manual hold"
    if not interface.replacement_ref.strip():
        return False, "missing replacement reference"
    if not interface.replacement_verified:
        return False, "replacement not verified"
    if window_calls > 0:
        return False, f"{window_calls} call(s) in review window"
    return True, "no measured usage in review window"


def _ensure_datetime(value: datetime | None) -> datetime | None:
    if value is None:
        return None
    if value.tzinfo is None:
        return value.replace(tzinfo=timezone.utc)
    return value


def _required(value: str | None, field: str) -> str:
    cleaned = _clean_value(value)
    if not cleaned:
        raise ValueError(f"{field} is required")
    return cleaned


def _clean_value(value: str | None, default: str = "") -> str:
    return str(value or default).strip()


def _clean_bucket(value: str | None) -> str:
    cleaned = _clean_value(value)
    return cleaned or UNKNOWN_BUCKET


def _validate_kind(kind: str) -> str:
    cleaned = _required(kind, "interface_kind")
    if cleaned not in VALID_INTERFACE_KINDS:
        raise ValueError(f"interface_kind must be one of {sorted(VALID_INTERFACE_KINDS)}")
    return cleaned


def _validate_status(status: str) -> str:
    cleaned = _required(status, "status")
    if cleaned not in VALID_INTERFACE_STATUSES:
        raise ValueError(f"status must be one of {sorted(VALID_INTERFACE_STATUSES)}")
    return cleaned
