"""Control-plane SQLAlchemy models for ``absolutems_control_plane``.

These models define the platform metadata layer that wraps the existing apps.
They are intentionally isolated from application-specific schemas under
``/apps`` and can be migrated later without touching existing business logic.
"""

from __future__ import annotations

import uuid
from datetime import datetime

from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, Integer, Numeric, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship

from config.db import Base


def _uuid() -> str:
    return str(uuid.uuid4())


class TimestampMixin:
    """Shared created/updated timestamps."""

    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
    updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)


class Tenant(Base, TimestampMixin):
    """A customer tenant registered in the control plane."""

    __tablename__ = "tenants"

    tenant_id: Mapped[str] = mapped_column(String(64), primary_key=True)
    slug: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
    name: Mapped[str] = mapped_column(String(255), nullable=False)
    status: Mapped[str] = mapped_column(String(32), default="active", nullable=False)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
    plan_id: Mapped[str | None] = mapped_column(String(64), ForeignKey("plans.plan_id"), nullable=True)
    data_db_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
    storage_key: Mapped[str | None] = mapped_column(String(128), nullable=True)
    timezone: Mapped[str | None] = mapped_column(String(64), nullable=True)
    metadata_json: Mapped[dict | None] = mapped_column("metadata", JSON, nullable=True)

    domains: Mapped[list["TenantDomain"]] = relationship(back_populates="tenant", cascade="all, delete-orphan")
    apps: Mapped[list["TenantApp"]] = relationship(back_populates="tenant", cascade="all, delete-orphan")
    limits: Mapped[list["TenantLimit"]] = relationship(back_populates="tenant", cascade="all, delete-orphan")
    usage_records: Mapped[list["TenantUsage"]] = relationship(back_populates="tenant", cascade="all, delete-orphan")
    secrets: Mapped[list["TenantSecret"]] = relationship(back_populates="tenant", cascade="all, delete-orphan")
    integrations: Mapped[list["TenantIntegration"]] = relationship(back_populates="tenant", cascade="all, delete-orphan")
    audit_events: Mapped[list["AuditEvent"]] = relationship(back_populates="tenant", cascade="all, delete-orphan")
    jobs: Mapped[list["JobRegistryRecord"]] = relationship(back_populates="tenant", cascade="all, delete-orphan")


class TenantDomain(Base, TimestampMixin):
    """Domains assigned to a tenant."""

    __tablename__ = "tenant_domains"
    __table_args__ = (UniqueConstraint("domain", name="uq_tenant_domains_domain"),)

    domain_id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
    tenant_id: Mapped[str] = mapped_column(String(64), ForeignKey("tenants.tenant_id"), nullable=False, index=True)
    domain: Mapped[str] = mapped_column(String(255), nullable=False)
    is_primary: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
    is_verified: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)

    tenant: Mapped["Tenant"] = relationship(back_populates="domains")


class App(Base, TimestampMixin):
    """App catalog entry for an existing module under ``/apps``."""

    __tablename__ = "apps"

    app_id: Mapped[str] = mapped_column(String(64), primary_key=True)
    display_name: Mapped[str] = mapped_column(String(255), nullable=False)
    module_path: Mapped[str] = mapped_column(String(255), nullable=False)
    route_prefix: Mapped[str] = mapped_column(String(255), nullable=False)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
    metadata_json: Mapped[dict | None] = mapped_column("metadata", JSON, nullable=True)

    tenant_apps: Mapped[list["TenantApp"]] = relationship(back_populates="app")
    tenant_limits: Mapped[list["TenantLimit"]] = relationship(back_populates="app")
    tenant_usage: Mapped[list["TenantUsage"]] = relationship(back_populates="app")
    tenant_secrets: Mapped[list["TenantSecret"]] = relationship(back_populates="app")
    integrations: Mapped[list["TenantIntegration"]] = relationship(back_populates="app")
    audit_events: Mapped[list["AuditEvent"]] = relationship(back_populates="app")
    jobs: Mapped[list["JobRegistryRecord"]] = relationship(back_populates="app")


class TenantApp(Base, TimestampMixin):
    """App enablement and routing settings per tenant."""

    __tablename__ = "tenant_apps"
    __table_args__ = (UniqueConstraint("tenant_id", "app_id", name="uq_tenant_apps_tenant_app"),)

    tenant_app_id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
    tenant_id: Mapped[str] = mapped_column(String(64), ForeignKey("tenants.tenant_id"), nullable=False, index=True)
    app_id: Mapped[str] = mapped_column(String(64), ForeignKey("apps.app_id"), nullable=False, index=True)
    is_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
    config_json: Mapped[dict | None] = mapped_column("config", JSON, nullable=True)

    tenant: Mapped["Tenant"] = relationship(back_populates="apps")
    app: Mapped["App"] = relationship(back_populates="tenant_apps")


class Plan(Base, TimestampMixin):
    """Commercial plan for tenant entitlements."""

    __tablename__ = "plans"

    plan_id: Mapped[str] = mapped_column(String(64), primary_key=True)
    name: Mapped[str] = mapped_column(String(255), nullable=False)
    code: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
    price_monthly: Mapped[float | None] = mapped_column(Numeric(12, 2), nullable=True)
    currency: Mapped[str] = mapped_column(String(16), default="AUD", nullable=False)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    metadata_json: Mapped[dict | None] = mapped_column("metadata", JSON, nullable=True)

    tenants: Mapped[list["Tenant"]] = relationship()


class TenantLimit(Base, TimestampMixin):
    """Effective limits applied to a tenant or tenant-app pairing."""

    __tablename__ = "tenant_limits"
    __table_args__ = (UniqueConstraint("tenant_id", "app_id", "limit_name", name="uq_tenant_limits_scope"),)

    tenant_limit_id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
    tenant_id: Mapped[str] = mapped_column(String(64), ForeignKey("tenants.tenant_id"), nullable=False, index=True)
    app_id: Mapped[str | None] = mapped_column(String(64), ForeignKey("apps.app_id"), nullable=True, index=True)
    limit_name: Mapped[str] = mapped_column(String(128), nullable=False)
    limit_value: Mapped[int | None] = mapped_column(Integer, nullable=True)
    window_seconds: Mapped[int | None] = mapped_column(Integer, nullable=True)
    overage_policy: Mapped[str | None] = mapped_column(String(64), nullable=True)
    metadata_json: Mapped[dict | None] = mapped_column("metadata", JSON, nullable=True)

    tenant: Mapped["Tenant"] = relationship(back_populates="limits")
    app: Mapped["App" | None] = relationship(back_populates="tenant_limits")


class TenantUsage(Base):
    """Recorded usage data for billing, quotas, and analytics."""

    __tablename__ = "tenant_usage"

    usage_id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
    tenant_id: Mapped[str] = mapped_column(String(64), ForeignKey("tenants.tenant_id"), nullable=False, index=True)
    app_id: Mapped[str | None] = mapped_column(String(64), ForeignKey("apps.app_id"), nullable=True, index=True)
    metric_name: Mapped[str] = mapped_column(String(128), nullable=False)
    metric_value: Mapped[float] = mapped_column(Numeric(18, 4), nullable=False, default=0)
    usage_period_start: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    usage_period_end: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    recorded_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
    metadata_json: Mapped[dict | None] = mapped_column("metadata", JSON, nullable=True)

    tenant: Mapped["Tenant"] = relationship(back_populates="usage_records")
    app: Mapped["App" | None] = relationship(back_populates="tenant_usage")


class TenantSecret(Base, TimestampMixin):
    """Encrypted secrets scoped to a tenant and optional app."""

    __tablename__ = "tenant_secrets"
    __table_args__ = (UniqueConstraint("tenant_id", "app_id", "secret_name", name="uq_tenant_secrets_scope"),)

    tenant_secret_id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
    tenant_id: Mapped[str] = mapped_column(String(64), ForeignKey("tenants.tenant_id"), nullable=False, index=True)
    app_id: Mapped[str | None] = mapped_column(String(64), ForeignKey("apps.app_id"), nullable=True, index=True)
    secret_name: Mapped[str] = mapped_column(String(128), nullable=False)
    secret_value_encrypted: Mapped[str] = mapped_column(Text, nullable=False)
    key_version: Mapped[str | None] = mapped_column(String(64), nullable=True)
    metadata_json: Mapped[dict | None] = mapped_column("metadata", JSON, nullable=True)

    tenant: Mapped["Tenant"] = relationship(back_populates="secrets")
    app: Mapped["App" | None] = relationship(back_populates="tenant_secrets")


class TenantIntegration(Base, TimestampMixin):
    """External integrations configured per tenant and app."""

    __tablename__ = "tenant_integrations"
    __table_args__ = (UniqueConstraint("tenant_id", "app_id", "integration_name", name="uq_tenant_integrations_scope"),)

    tenant_integration_id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
    tenant_id: Mapped[str] = mapped_column(String(64), ForeignKey("tenants.tenant_id"), nullable=False, index=True)
    app_id: Mapped[str | None] = mapped_column(String(64), ForeignKey("apps.app_id"), nullable=True, index=True)
    integration_name: Mapped[str] = mapped_column(String(128), nullable=False)
    status: Mapped[str] = mapped_column(String(32), default="active", nullable=False)
    config_json: Mapped[dict | None] = mapped_column("config", JSON, nullable=True)
    secret_ref: Mapped[str | None] = mapped_column(String(128), nullable=True)

    tenant: Mapped["Tenant"] = relationship(back_populates="integrations")
    app: Mapped["App" | None] = relationship(back_populates="integrations")


class AuditEvent(Base):
    """Immutable audit trail for administrative and security actions."""

    __tablename__ = "audit_events"

    audit_event_id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
    tenant_id: Mapped[str | None] = mapped_column(String(64), ForeignKey("tenants.tenant_id"), nullable=True, index=True)
    app_id: Mapped[str | None] = mapped_column(String(64), ForeignKey("apps.app_id"), nullable=True, index=True)
    actor_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
    actor_type: Mapped[str | None] = mapped_column(String(64), nullable=True)
    event_type: Mapped[str] = mapped_column(String(128), nullable=False)
    status: Mapped[str] = mapped_column(String(32), default="success", nullable=False)
    request_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
    ip_address: Mapped[str | None] = mapped_column(String(64), nullable=True)
    payload_json: Mapped[dict | None] = mapped_column("payload", JSON, nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)

    tenant: Mapped["Tenant" | None] = relationship(back_populates="audit_events")
    app: Mapped["App" | None] = relationship(back_populates="audit_events")


class JobRegistryRecord(Base, TimestampMixin):
    """Lifecycle records for background jobs submitted through the platform."""

    __tablename__ = "job_registry"

    job_id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_uuid)
    tenant_id: Mapped[str | None] = mapped_column(String(64), ForeignKey("tenants.tenant_id"), nullable=True, index=True)
    app_id: Mapped[str | None] = mapped_column(String(64), ForeignKey("apps.app_id"), nullable=True, index=True)
    job_name: Mapped[str] = mapped_column(String(128), nullable=False)
    queue_name: Mapped[str] = mapped_column(String(64), default="default", nullable=False)
    status: Mapped[str] = mapped_column(String(32), default="queued", nullable=False)
    payload_json: Mapped[dict | None] = mapped_column("payload", JSON, nullable=True)
    result_json: Mapped[dict | None] = mapped_column("result", JSON, nullable=True)
    attempts: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
    finished_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)

    tenant: Mapped["Tenant" | None] = relationship(back_populates="jobs")
    app: Mapped["App" | None] = relationship(back_populates="jobs")
