from __future__ import annotations

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

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

from app.db.base_class import Base


class ExpenseClaim(Base):
    __tablename__ = "expense_claims"
    __table_args__ = (
        UniqueConstraint("tenant_id", "id", name="uq_expense_claims_tenant_id"),
        UniqueConstraint(
            "tenant_id",
            "claim_no",
            name="uq_expense_claims_tenant_claim_no",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "employee_id"],
            ["employees.tenant_id", "employees.id"],
            name="fk_expense_claims_tenant_employee",
            ondelete="RESTRICT",
        ),
        ForeignKeyConstraint(
            ["tenant_id", "department_id"],
            ["organization_units.tenant_id", "organization_units.id"],
            name="fk_expense_claims_tenant_department",
            ondelete="RESTRICT",
        ),
        Index("ix_expense_claims_tenant_status", "tenant_id", "status"),
        Index("ix_expense_claims_tenant_occurred", "tenant_id", "occurred_at"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    tenant_id: Mapped[str] = mapped_column(
        ForeignKey("tenants.tenant_id", ondelete="RESTRICT"),
        nullable=False,
        server_default="default",
    )
    claim_no: Mapped[str] = mapped_column(String(50), index=True)
    employee_id: Mapped[str | None] = mapped_column(nullable=True, index=True)
    employee_name: Mapped[str] = mapped_column(String(100), index=True)
    department_id: Mapped[str | None] = mapped_column(nullable=True, index=True)
    department_name: Mapped[str] = mapped_column(String(100), index=True)
    project_code: Mapped[str | None] = mapped_column(String(50), nullable=True)
    expense_type: Mapped[str] = mapped_column(String(50), index=True)
    reason: Mapped[str] = mapped_column(Text())
    location: Mapped[str] = mapped_column(String(100))
    amount: Mapped[Decimal] = mapped_column(Numeric(12, 2))
    currency: Mapped[str] = mapped_column(String(10), default="CNY")
    invoice_count: Mapped[int] = mapped_column(Integer, default=0)
    occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
    submitted_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True, index=True
    )
    status: Mapped[str] = mapped_column(String(30), index=True)
    approval_stage: Mapped[str | None] = mapped_column(String(50), nullable=True)
    risk_flags_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
    hermes_scanned_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    hermes_risk_flag: Mapped[bool] = mapped_column(Boolean, default=False, index=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
    )

    employee = relationship(
        "Employee",
        foreign_keys=[tenant_id, employee_id],
    )
    items = relationship(
        "ExpenseClaimItem",
        back_populates="claim",
        cascade="all, delete-orphan",
        order_by="asc(ExpenseClaimItem.item_date)",
    )

    @property
    def employee_position(self) -> str | None:
        return str(self.employee.position).strip() if self.employee is not None and self.employee.position else None

    @property
    def employee_no(self) -> str | None:
        return str(self.employee.employee_no).strip() if self.employee is not None and self.employee.employee_no else None

    @property
    def employee_email(self) -> str | None:
        return str(self.employee.email).strip() if self.employee is not None and self.employee.email else None

    @property
    def employee_grade(self) -> str | None:
        return str(self.employee.grade).strip() if self.employee is not None and self.employee.grade else None

    @property
    def manager_name(self) -> str | None:
        if self.employee is None:
            return None
        if self.employee.manager is not None and self.employee.manager.name:
            return str(self.employee.manager.name).strip() or None
        if self.employee.organization_unit is not None and self.employee.organization_unit.manager_name:
            return str(self.employee.organization_unit.manager_name).strip() or None
        return None

    @property
    def finance_owner_name(self) -> str | None:
        if self.employee is None or not self.employee.finance_owner_name:
            return None
        return str(self.employee.finance_owner_name).strip() or None

    @property
    def role_labels(self) -> list[str]:
        if self.employee is None or not self.employee.roles:
            return []
        return [str(role.name).strip() for role in sorted(self.employee.roles, key=lambda item: item.name) if role.name]


class ExpenseClaimItem(Base):
    __tablename__ = "expense_claim_items"

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    claim_id: Mapped[str] = mapped_column(ForeignKey("expense_claims.id"), index=True)
    item_date: Mapped[date] = mapped_column(Date(), index=True)
    item_type: Mapped[str] = mapped_column(String(50))
    item_reason: Mapped[str] = mapped_column(Text())
    item_location: Mapped[str] = mapped_column(String(100))
    item_note: Mapped[str] = mapped_column(Text(), default="")
    item_amount: Mapped[Decimal] = mapped_column(Numeric(12, 2))
    invoice_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
    )

    claim = relationship("ExpenseClaim", back_populates="items")

    @property
    def is_system_generated(self) -> bool:
        return str(self.item_type or "").strip().lower() in {"travel_allowance"}


class AccountsReceivableRecord(Base):
    __tablename__ = "accounts_receivable"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "receivable_no",
            name="uq_accounts_receivable_tenant_no",
        ),
        Index("ix_accounts_receivable_tenant_customer", "tenant_id", "customer_id"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    tenant_id: Mapped[str] = mapped_column(
        ForeignKey("tenants.tenant_id", ondelete="RESTRICT"),
        nullable=False,
        server_default="default",
    )
    receivable_no: Mapped[str] = mapped_column(String(50), index=True)
    customer_id: Mapped[str] = mapped_column(String(64), index=True)
    customer_name: Mapped[str] = mapped_column(String(120), index=True)
    contract_no: Mapped[str | None] = mapped_column(String(100), nullable=True)
    invoice_no: Mapped[str | None] = mapped_column(String(100), nullable=True)
    amount_receivable: Mapped[Decimal] = mapped_column(Numeric(12, 2))
    amount_received: Mapped[Decimal] = mapped_column(Numeric(12, 2))
    amount_outstanding: Mapped[Decimal] = mapped_column(Numeric(12, 2))
    currency: Mapped[str] = mapped_column(String(10), default="CNY")
    posting_date: Mapped[date] = mapped_column(Date(), index=True)
    due_date: Mapped[date] = mapped_column(Date(), index=True)
    aging_days: Mapped[int] = mapped_column(Integer, default=0)
    status: Mapped[str] = mapped_column(String(30), index=True)
    risk_flags_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
    )


class AccountsPayableRecord(Base):
    __tablename__ = "accounts_payable"
    __table_args__ = (
        UniqueConstraint(
            "tenant_id",
            "payable_no",
            name="uq_accounts_payable_tenant_no",
        ),
        Index("ix_accounts_payable_tenant_vendor", "tenant_id", "vendor_id"),
    )

    id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
    tenant_id: Mapped[str] = mapped_column(
        ForeignKey("tenants.tenant_id", ondelete="RESTRICT"),
        nullable=False,
        server_default="default",
    )
    payable_no: Mapped[str] = mapped_column(String(50), index=True)
    vendor_id: Mapped[str] = mapped_column(String(64), index=True)
    vendor_name: Mapped[str] = mapped_column(String(120), index=True)
    invoice_no: Mapped[str | None] = mapped_column(String(100), nullable=True)
    amount_payable: Mapped[Decimal] = mapped_column(Numeric(12, 2))
    amount_paid: Mapped[Decimal] = mapped_column(Numeric(12, 2))
    amount_outstanding: Mapped[Decimal] = mapped_column(Numeric(12, 2))
    currency: Mapped[str] = mapped_column(String(10), default="CNY")
    posting_date: Mapped[date] = mapped_column(Date(), index=True)
    due_date: Mapped[date] = mapped_column(Date(), index=True)
    aging_days: Mapped[int] = mapped_column(Integer, default=0)
    status: Mapped[str] = mapped_column(String(30), index=True)
    risk_flags_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
    )
