from sqlalchemy import (
    Integer,
    ForeignKey,
    String,
    Table,
    Float,
    DateTime,
    Text,
    Boolean,
    Column,
)
from sqlalchemy.orm import relationship, Mapped, mapped_column
from sqlalchemy.sql import func
from src.core.utils import constants
from datetime import datetime
from src.core.utils.enums import InvoiceStatusTypes
from src.apps.base.models.base import Base
from sqlalchemy.ext.hybrid import hybrid_property
from src.core.config import settings
# TODO: Fix this import - transactions model doesn't exist yet
from src.apps.transactions.models.transactions import transactions_invoices_map
from typing import Optional, List


invoice_attachments_map = Table(
    "invoices_files",
    Base.metadata,
    Column("invoice_id", Integer, ForeignKey("invoices.id")),
    Column("file_id", Integer, ForeignKey("files.id")),
)

invoice_notes_map = Table(
    "invoices_notes",
    Base.metadata,
    Column("invoice_id", Integer, ForeignKey("invoices.id")),
    Column("note_id", Integer, ForeignKey("notes.id")),
)

# TODO: Uncomment when Reminder model is created
invoice_reminder_map = Table(
    "invoices_reminders",
    Base.metadata,
    Column("invoice_id", Integer, ForeignKey("invoices.id")),
    Column("reminder_id", Integer, ForeignKey("reminders.id")),
)


class Invoice(Base):
    """
    Invoice Model: ORM class for Invoice Entity
    """

    __tablename__ = "invoices"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True, autoincrement=True)
    invoice_id: Mapped[str] = mapped_column(String(255), unique=True, index=True)
    amount: Mapped[float] = mapped_column(Float, default=0.0)
    due_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    billing_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    next_due_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    tax_fee: Mapped[float] = mapped_column(Float, default=0.0)
    shipping_fee: Mapped[float] = mapped_column(Float, default=0.0)
    tip: Mapped[float] = mapped_column(Float, default=0.0)
    discount: Mapped[float] = mapped_column(Float, default=0.0)
    surcharge: Mapped[float] = mapped_column(Float, default=0.0)
    charge: Mapped[float] = mapped_column(Float, default=0.0)
    additional_fee: Mapped[float] = mapped_column(Float, default=0.0)
    status: Mapped[int] = mapped_column(Integer, default=1)
    comments: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
    invoice_literal: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    reference: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    paid_amount: Mapped[float] = mapped_column(Float, default=0.0)
    paid_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)

    created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
    updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, onupdate=func.now())
    deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    is_surcharge_enabled: Mapped[bool] = mapped_column(Boolean, default=False)
    surcharge_type: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
    following: Mapped[bool] = mapped_column(Boolean, default=False)
    sequence_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)

    invoice_line_items: Mapped[List["InvoiceLineItems"]] = relationship("InvoiceLineItems", back_populates="invoice")

    payment_request_id: Mapped[int] = mapped_column(Integer, ForeignKey("payment_requests.id"))
    payment_request: Mapped["PaymentRequest"] = relationship("PaymentRequest")

    merchant_id: Mapped[int] = mapped_column(Integer, ForeignKey("merchants.id"))
    merchant: Mapped["Merchant"] = relationship("Merchant")

    customer_id: Mapped[int] = mapped_column(Integer, ForeignKey("customers.id"))
    customer: Mapped["Customer"] = relationship("Customer")

    payer_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("customer_contacts.id"), nullable=True)
    payer: Mapped[Optional["CustomerContact"]] = relationship("CustomerContact", foreign_keys=[payer_id], uselist=False)

    approver_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("customer_contacts.id"), nullable=True)
    approver: Mapped[Optional["CustomerContact"]] = relationship(
        "CustomerContact", foreign_keys=[approver_id], uselist=False
    )

    payment_links: Mapped[List["PaymentRequestLinks"]] = relationship("PaymentRequestLinks", back_populates="invoice")

    # TODO: Uncomment when transactions model is created
    transactions: Mapped[List["Transactions"]] = relationship(
        "Transactions", secondary=transactions_invoices_map, back_populates="invoices"
    )

    adjustment: Mapped[Optional["InvoiceAdjustment"]] = relationship("InvoiceAdjustment", back_populates="invoice")

    attachments: Mapped[List["File"]] = relationship("File", secondary=invoice_attachments_map)
    # TODO: Uncomment when Reminder model is created
    reminders: Mapped[List["Reminder"]] = relationship("Reminder", secondary=invoice_reminder_map)

    @hybrid_property
    def download_url(self) -> str:
        url = f"{settings.SERVER_HOST}{settings.API_ROUTE_PREFIX}/invoices/{self.invoice_id}/download"
        return url

    @hybrid_property
    def status_text(self) -> str:
        return next(
            (
                " ".join(c.name.lower().split("_"))
                for c in InvoiceStatusTypes
                if c.value == self.status
            ),
            constants.DEFAULT_STATUS_TEXT,
        )

    @hybrid_property
    def days_until_due(self) -> int:
        if self.status in [InvoiceStatusTypes.OVERDUE, InvoiceStatusTypes.PENDING]:
            if self.due_date:
                today_date = datetime.now().date()
                difference = self.due_date.date() - today_date
                return (
                    difference.days
                    if self.status == InvoiceStatusTypes.OVERDUE
                    else -difference.days
                )
            else:
                return 0
        return 0

    @hybrid_property
    def due_amount(self) -> int:
        if self.status == InvoiceStatusTypes.PAID:
            return 0
        return self.amount

    @hybrid_property
    def can_edit(self) -> bool:
        return self.payment_request.can_edit
