from __future__ import annotations

import uuid
from datetime import datetime
from typing import Any, Optional

from sqlalchemy import DateTime, ForeignKey, Index, Integer, String, Text, UniqueConstraint, Uuid
from sqlalchemy import Enum as SAEnum
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import JSON

from app.core.time import utc_now
from app.domain.enums import ActivationStatus, LicenceEventType, LicenceStatus, LicenceType
from app.models.database import Base


def utc_datetime_column() -> Mapped[datetime]:
    return mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)


json_type = JSON().with_variant(JSONB, "postgresql")


class TimestampMixin:
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        default=utc_now,
        onupdate=utc_now,
        nullable=False,
    )


class Customer(Base, TimestampMixin):
    __tablename__ = "customers"
    __table_args__ = (
        UniqueConstraint("external_source", "external_customer_id", name="uq_customer_external_identity"),
    )

    id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
    external_source: Mapped[Optional[str]] = mapped_column(String(80), nullable=True, index=True)
    external_customer_id: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, index=True)
    external_customer_reference: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, index=True)
    email: Mapped[Optional[str]] = mapped_column(String(320), nullable=True, index=True)

    licences: Mapped[list[Licence]] = relationship(back_populates="customer")


class Licence(Base, TimestampMixin):
    __tablename__ = "licences"

    id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
    public_licence_identifier: Mapped[str] = mapped_column(String(32), nullable=False, unique=True, index=True)
    licence_key_suffix: Mapped[str] = mapped_column(String(12), nullable=False, index=True)
    licence_key_hash: Mapped[str] = mapped_column(Text, nullable=False)
    licence_key_hash_version: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
    licence_type: Mapped[LicenceType] = mapped_column(SAEnum(LicenceType), nullable=False)
    status: Mapped[LicenceStatus] = mapped_column(SAEnum(LicenceStatus), default=LicenceStatus.ACTIVE, nullable=False)
    customer_id: Mapped[Optional[uuid.UUID]] = mapped_column(Uuid(as_uuid=True), ForeignKey("customers.id"), nullable=True)
    activation_limit: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
    subscription_expiry: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
    update_entitlement_expiry: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
    support_entitlement_expiry: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
    metadata_json: Mapped[dict[str, Any]] = mapped_column(json_type, default=dict, nullable=False)

    customer: Mapped[Optional[Customer]] = relationship(back_populates="licences")
    activations: Mapped[list[Activation]] = relationship(back_populates="licence")
    events: Mapped[list[LicenceEvent]] = relationship(back_populates="licence")


class Activation(Base, TimestampMixin):
    __tablename__ = "activations"
    __table_args__ = (
        UniqueConstraint("licence_id", "installation_id", name="uq_activation_licence_installation"),
        Index("ix_activation_licence_status", "licence_id", "status"),
    )

    id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
    licence_id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), ForeignKey("licences.id"), nullable=False)
    installation_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
    activation_token_identifier: Mapped[str] = mapped_column(String(32), nullable=False, unique=True, index=True)
    activation_token_suffix: Mapped[str] = mapped_column(String(12), nullable=False)
    activation_token_hash: Mapped[str] = mapped_column(Text, nullable=False)
    activation_token_hash_version: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
    status: Mapped[ActivationStatus] = mapped_column(
        SAEnum(ActivationStatus),
        default=ActivationStatus.ACTIVE,
        nullable=False,
        index=True,
    )
    activated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, nullable=False)
    last_validated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
    deactivated_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)

    licence: Mapped[Licence] = relationship(back_populates="activations")


class LicenceEvent(Base, TimestampMixin):
    __tablename__ = "licence_events"
    __table_args__ = (
        Index("ix_licence_events_licence_created", "licence_id", "created_at"),
    )

    id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
    licence_id: Mapped[Optional[uuid.UUID]] = mapped_column(Uuid(as_uuid=True), ForeignKey("licences.id"), nullable=True)
    activation_id: Mapped[Optional[uuid.UUID]] = mapped_column(Uuid(as_uuid=True), ForeignKey("activations.id"), nullable=True)
    event_type: Mapped[LicenceEventType] = mapped_column(SAEnum(LicenceEventType), nullable=False)
    result: Mapped[str] = mapped_column(String(80), nullable=False)
    public_licence_identifier: Mapped[Optional[str]] = mapped_column(String(32), nullable=True, index=True)
    request_correlation_id: Mapped[Optional[str]] = mapped_column(String(80), nullable=True)
    detail: Mapped[dict[str, Any]] = mapped_column(json_type, default=dict, nullable=False)

    licence: Mapped[Optional[Licence]] = relationship(back_populates="events")


class IntegrationIdempotencyRecord(Base, TimestampMixin):
    __tablename__ = "integration_idempotency_records"
    __table_args__ = (
        UniqueConstraint("source", "idempotency_key", name="uq_integration_idempotency_source_key"),
    )

    id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
    source: Mapped[str] = mapped_column(String(80), nullable=False, index=True)
    idempotency_key: Mapped[str] = mapped_column(String(120), nullable=False)
    operation: Mapped[str] = mapped_column(String(120), nullable=False)
    licence_id: Mapped[Optional[uuid.UUID]] = mapped_column(Uuid(as_uuid=True), ForeignKey("licences.id"), nullable=True)
    response_json: Mapped[dict[str, Any]] = mapped_column(json_type, default=dict, nullable=False)
