"""Data governance: provider/filing registry, data-quality/conflict tracking, users, audit log."""
from __future__ import annotations

from datetime import datetime
from typing import Optional

from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, String, func
from sqlalchemy.orm import Mapped, mapped_column

from app.models.base import Base, TimestampMixin, uuid_pk


class Source(Base, TimestampMixin):
    """One row per (provider/filing) ingestion event — the top of the source hierarchy chain
    (docs/DATA_SOURCES.md §4) and the archive of the raw payload for replay (§44)."""

    __tablename__ = "sources"

    id: Mapped[str] = uuid_pk()
    provider: Mapped[str] = mapped_column(String(32), index=True)  # "FMP" | "EODHD" | "SEC_EDGAR" | "DEMO"
    provider_tier: Mapped[str] = mapped_column(String(16))  # OFFICIAL_FILING | PRIMARY | SECONDARY | CALCULATED
    is_demo: Mapped[bool] = mapped_column(Boolean, default=False, index=True)  # DATA_SOURCES.md §9 — hard separation
    retrieved_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    endpoint: Mapped[Optional[str]] = mapped_column(String(256), nullable=True)
    raw_payload: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)  # deliberate JSON exception, DATA_MODEL.md
    content_hash: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, index=True)


class DataQuality(Base, TimestampMixin):
    """Per-data-point quality/conflict record (spec §9)."""

    __tablename__ = "data_quality"

    id: Mapped[str] = uuid_pk()
    entity_table: Mapped[str] = mapped_column(String(64))   # e.g. "income_statements"
    entity_id: Mapped[str] = mapped_column(String(64), index=True)
    field: Mapped[str] = mapped_column(String(64))
    status: Mapped[str] = mapped_column(String(16))  # VERIFIED|CALCULATED|ESTIMATED|ASSUMPTION|MISSING|STALE|CONFLICTING
    source_a_id: Mapped[Optional[str]] = mapped_column(ForeignKey("sources.id"), nullable=True)
    value_a: Mapped[Optional[float]] = mapped_column(nullable=True)
    source_b_id: Mapped[Optional[str]] = mapped_column(ForeignKey("sources.id"), nullable=True)
    value_b: Mapped[Optional[float]] = mapped_column(nullable=True)
    selected_value: Mapped[Optional[float]] = mapped_column(nullable=True)
    selected_source_id: Mapped[Optional[str]] = mapped_column(ForeignKey("sources.id"), nullable=True)
    selection_reason: Mapped[Optional[str]] = mapped_column(nullable=True)


class RevokedToken(Base):
    """Revoked/rotated refresh-token store (StockLab overhaul, final engineering pass, Part A4).

    Deliberately scoped to refresh tokens only -- access tokens are short-lived
    (Settings.ACCESS_TOKEN_EXPIRE_MINUTES, 30 min default) and validated on every authenticated
    request (app/api/v1/deps.py::get_current_user); adding a DB round-trip to that path for every
    request would be the "incompatible auth rewrite" this pass's instructions said to avoid, for a
    marginal gain given how short-lived access tokens already are. Refresh tokens are long-lived
    (Settings.REFRESH_TOKEN_EXPIRE_DAYS, 14 days default) and used rarely (once per session
    renewal) -- the theft window that actually matters, and where a DB check is cheap relative to
    how often it runs.

    No FK constraint enforced on user_id on purpose: a token can outlive its user row in edge cases
    (revoke-then-delete-account ordering), and this table's only real purpose is jti lookup, not
    joining back to User -- keeping it nullable/unconstrained avoids a fragile cross-table
    dependency for a security-relevant write path that should never itself fail to write.
    """

    __tablename__ = "revoked_tokens"

    id: Mapped[str] = uuid_pk()
    jti: Mapped[str] = mapped_column(String(64), unique=True, index=True)
    user_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, index=True)
    token_type: Mapped[str] = mapped_column(String(16), default="refresh")  # only "refresh" is ever written today
    revoked_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    # Copy of the original token's own `exp` claim -- lets prune_expired_revoked_tokens() (see
    # app/core/token_revocation.py) delete rows once decode_token() would reject that jti's token
    # on expiry alone anyway, so this table doesn't grow forever.
    expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
    reason: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)  # "logout" | "rotated" | None


class User(Base, TimestampMixin):
    __tablename__ = "users"

    id: Mapped[str] = uuid_pk()
    email: Mapped[str] = mapped_column(String(256), unique=True, index=True)
    hashed_password: Mapped[str] = mapped_column(String(256))
    display_name: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)
    is_admin: Mapped[bool] = mapped_column(Boolean, default=False)


class AuditLogEntry(Base):
    __tablename__ = "audit_log"

    id: Mapped[str] = uuid_pk()
    occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
    actor_user_id: Mapped[Optional[str]] = mapped_column(ForeignKey("users.id"), nullable=True)
    event_type: Mapped[str] = mapped_column(String(64), index=True)  # LOGIN | LOGIN_FAILED | TOKEN_REFRESH | LOGOUT | ...
    detail: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)  # never secrets — see SECURITY.md
    ip_address: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
