"""Phase 5.2 — Double-entry posting domain models."""
from __future__ import annotations

import uuid
from datetime import date, datetime
from decimal import Decimal

from sqlalchemy import (
    Boolean,
    Date,
    DateTime,
    ForeignKey,
    Index,
    Integer,
    Numeric,
    String,
    Text,
    UniqueConstraint,
    func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship

from app.core.database import Base
from app.models.base import TenantMixin, TimestampMixin, UUIDPrimaryKeyMixin
from app.models.types import (
    GUID,
    JournalStatus,
    PostingStatus,
    VoucherStatus,
)


class Voucher(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
    __tablename__ = "vouchers"

    fiscal_period_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
    voucher_number: Mapped[str] = mapped_column(String(50), nullable=False)
    voucher_date: Mapped[date] = mapped_column(Date, nullable=False)
    status: Mapped[VoucherStatus] = mapped_column(
        default=VoucherStatus.DRAFT, nullable=False
    )
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    reference_number: Mapped[str | None] = mapped_column(String(100), nullable=True)
    source_module: Mapped[str | None] = mapped_column(String(50), nullable=True)
    currency_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    total_debit: Mapped[Decimal] = mapped_column(
        Numeric(18, 4), default=Decimal("0"), nullable=False
    )
    total_credit: Mapped[Decimal] = mapped_column(
        Numeric(18, 4), default=Decimal("0"), nullable=False
    )
    posted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    posted_by: Mapped[str | None] = mapped_column(String(100), nullable=True)
    reversed_voucher_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    created_by: Mapped[str | None] = mapped_column(String(100), nullable=True)

    lines: Mapped[list["VoucherLine"]] = relationship(
        back_populates="voucher", cascade="all, delete-orphan"
    )
    journal_entries: Mapped[list["JournalEntry"]] = relationship(back_populates="voucher")

    __table_args__ = (
        UniqueConstraint("tenant_id", "voucher_number", name="uq_voucher_tenant_number"),
        Index("ix_vouchers_tenant_status", "tenant_id", "status"),
        Index("ix_vouchers_fiscal_period", "tenant_id", "fiscal_period_id"),
    )


class VoucherLine(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
    __tablename__ = "voucher_lines"

    voucher_id: Mapped[uuid.UUID] = mapped_column(
        GUID(), ForeignKey("vouchers.id", ondelete="CASCADE"), nullable=False
    )
    line_number: Mapped[int] = mapped_column(Integer, nullable=False)
    account_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
    debit: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False)
    credit: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    cost_center_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    project_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    currency_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    exchange_rate: Mapped[Decimal | None] = mapped_column(Numeric(18, 8), nullable=True)

    voucher: Mapped["Voucher"] = relationship(back_populates="lines")

    __table_args__ = (
        Index("ix_voucher_lines_voucher_id", "voucher_id"),
        Index("ix_voucher_lines_account_id", "tenant_id", "account_id"),
    )


class Journal(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
    __tablename__ = "journals"

    code: Mapped[str] = mapped_column(String(50), nullable=False)
    name: Mapped[str] = mapped_column(String(255), nullable=False)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)

    __table_args__ = (
        UniqueConstraint("tenant_id", "code", name="uq_journal_tenant_code"),
    )


class JournalEntry(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
    """Immutable after posting — only created via Posting Engine."""

    __tablename__ = "journal_entries"

    voucher_id: Mapped[uuid.UUID] = mapped_column(
        GUID(), ForeignKey("vouchers.id", ondelete="RESTRICT"), nullable=False
    )
    journal_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    fiscal_period_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
    entry_number: Mapped[str] = mapped_column(String(50), nullable=False)
    entry_date: Mapped[date] = mapped_column(Date, nullable=False)
    status: Mapped[JournalStatus] = mapped_column(
        default=JournalStatus.DRAFT, nullable=False
    )
    account_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
    debit: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False)
    credit: Mapped[Decimal] = mapped_column(Numeric(18, 4), default=Decimal("0"), nullable=False)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    cost_center_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    project_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
    posted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    posted_by: Mapped[str | None] = mapped_column(String(100), nullable=True)

    voucher: Mapped["Voucher"] = relationship(back_populates="journal_entries")

    __table_args__ = (
        Index("ix_journal_entries_tenant_account", "tenant_id", "account_id"),
        Index("ix_journal_entries_voucher", "voucher_id"),
        Index("ix_journal_entries_period", "tenant_id", "fiscal_period_id"),
    )


class PostingLog(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
    __tablename__ = "posting_logs"

    voucher_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
    operation: Mapped[str] = mapped_column(String(50), nullable=False)
    status: Mapped[PostingStatus] = mapped_column(nullable=False)
    actor_user_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
    source_module: Mapped[str | None] = mapped_column(String(50), nullable=True)
    message: Mapped[str | None] = mapped_column(Text, nullable=True)
    correlation_id: Mapped[str | None] = mapped_column(String(100), nullable=True)

    __table_args__ = (Index("ix_posting_logs_voucher", "tenant_id", "voucher_id"),)


class PostingError(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
    __tablename__ = "posting_errors"

    voucher_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
    error_code: Mapped[str] = mapped_column(String(50), nullable=False)
    error_message: Mapped[str] = mapped_column(Text, nullable=False)
    field_name: Mapped[str | None] = mapped_column(String(100), nullable=True)

    __table_args__ = (Index("ix_posting_errors_voucher", "tenant_id", "voucher_id"),)


class PostingReference(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
    __tablename__ = "posting_references"

    voucher_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
    source_module: Mapped[str] = mapped_column(String(50), nullable=False)
    source_document_type: Mapped[str] = mapped_column(String(50), nullable=False)
    source_document_id: Mapped[str] = mapped_column(String(100), nullable=False)
    is_posted: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)

    __table_args__ = (
        UniqueConstraint(
            "tenant_id", "source_module", "source_document_type", "source_document_id",
            name="uq_posting_ref_source",
        ),
    )


class AccountingAuditLog(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
    __tablename__ = "accounting_audit_logs"

    operation: Mapped[str] = mapped_column(String(50), nullable=False)
    actor_user_id: Mapped[str] = mapped_column(String(100), nullable=False)
    source_module: Mapped[str | None] = mapped_column(String(50), nullable=True)
    voucher_number: Mapped[str | None] = mapped_column(String(50), nullable=True)
    reference_number: Mapped[str | None] = mapped_column(String(100), nullable=True)
    resource_type: Mapped[str] = mapped_column(String(50), nullable=False)
    resource_id: Mapped[str] = mapped_column(String(100), nullable=False)
    before_value: Mapped[str | None] = mapped_column(Text, nullable=True)
    after_value: Mapped[str | None] = mapped_column(Text, nullable=True)
    ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True)
    correlation_id: Mapped[str | None] = mapped_column(String(100), nullable=True)

    __table_args__ = (
        Index("ix_audit_logs_tenant_created", "tenant_id", "created_at"),
    )
