from datetime import datetime, timedelta
from sqlalchemy import Column, Integer, Text, DateTime, ForeignKey, Float, Boolean
from sqlalchemy.orm import relationship, Mapped, mapped_column
from sqlalchemy.sql import func
from sqlalchemy.sql.schema import Table
from sqlalchemy.sql.sqltypes import String
from typing import Dict, List, Optional, TYPE_CHECKING
from src.apps.base.models.base import Base
from sqlalchemy.ext.hybrid import hybrid_property
from src.core.utils.enums import (
    PaymentTypes,
    RecurringPaymentTimeIntervals,
    PaymentRequestType,
)
from src.apps.payment_requests.enums import (
    PaymentAuthorizationTypes,
    PaymentCurrencies,
    PaymentFrequencies,
    PaymentRequestStatusTypes,
    RecurringPaymentEndTypes,
    RecurringPaymentRepeatTypes,
    TaxType,
)
from dateutil.relativedelta import relativedelta

if TYPE_CHECKING:
    # Forward declarations for type checking only
    from typing import Any
    SplitPaymentRequests = Any
    Address = Any
    RecurringPaymentRequests = Any
    PaymentRequestLineItems = Any
    PaymentRequestProducts = Any
    PaymentMethod = Any
    PaymentRequestIntent = Any
    Transactions = Any
    PaymentRequestLinks = Any
    PaymentRequestCustomer = Any
    File = Any
    Note = Any
    Reminder = Any
    Merchant = Any
    User = Any
    PaymentRequestAdjustments = Any
    PaymentRequestAuthorizations = Any
    PaymentRequestApproval = Any


payment_request_attachments_map = Table(
    "payment_requests_files",
    Base.metadata,
    Column("payment_request_id", ForeignKey("payment_requests.id")),
    Column("file_id", ForeignKey("files.id")),
)

payment_request_notes_map = Table(
    "payment_requests_notes",
    Base.metadata,
    Column("payment_request_id", ForeignKey("payment_requests.id")),
    Column("note_id", ForeignKey("notes.id")),
)

# TODO: Uncomment when Reminder model is created
payment_request_reminder_map = Table(
    "payment_request_reminders",
    Base.metadata,
    Column("payment_request_id", ForeignKey("payment_requests.id")),
    Column("reminder_id", ForeignKey("reminders.id")),
)


class PaymentRequest(Base):
    """
    Payment Request Model: ORM class for Payment Request Entity
    """

    __tablename__ = "payment_requests"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True, autoincrement=True)
    payment_request_id: Mapped[Optional[str]] = mapped_column(String(50), index=True, unique=True, nullable=True)
    amount: Mapped[float] = mapped_column(Float, default=0.0)
    payment_frequency: Mapped[Optional[str]] = mapped_column(String(50), default=None)
    authorization_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
    status: Mapped[int] = mapped_column(Integer, default=PaymentRequestStatusTypes.CREATED.value)
    currency: Mapped[str] = mapped_column(String(50), default=PaymentCurrencies.USD.value)
    save_payment_method: Mapped[bool] = mapped_column(Boolean, default=False)
    allow_tip: Mapped[bool] = mapped_column(Boolean, default=False)
    is_certified: Mapped[bool] = mapped_column(Boolean, default=False)
    configure_adjustment: Mapped[bool] = mapped_column(Boolean, default=False)
    message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    title: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    billing_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    due_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    reference_id: Mapped[Optional[str]] = mapped_column(Text, nullable=True, unique=True)
    tax_percent: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
    payment_request_literal: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    enable_email: Mapped[bool] = mapped_column(Boolean, default=True)
    enable_sms: Mapped[bool] = mapped_column(Boolean, default=False)
    enable_email_receipt: Mapped[bool] = mapped_column(Boolean, default=False)
    enable_sms_receipt: Mapped[bool] = mapped_column(Boolean, default=False)
    shipping_fee: Mapped[float] = mapped_column(Float, default=0.0)
    require_billing_address: Mapped[bool] = mapped_column(Boolean, default=False)
    require_cvv: Mapped[bool] = mapped_column(Boolean, default=True)
    require_sms_authorization: Mapped[bool] = mapped_column(Boolean, default=False)
    require_shipping_address: Mapped[bool] = mapped_column(Boolean, default=False)
    require_signature_authorization: Mapped[bool] = mapped_column(Boolean, default=False)
    defined_by: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
    payment_request_type: Mapped[str] = mapped_column(
        String(20), default=PaymentRequestType.PAYMENT_REQUEST.value
    )
    payment_redirect_url: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    payment_split_frequency: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
    charge_description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    donation_description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    has_effective_date: Mapped[bool] = mapped_column(Boolean, default=False)
    is_message: Mapped[bool] = mapped_column(Boolean, default=False)
    reference: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    tax_type: Mapped[Optional[str]] = mapped_column(String(255), 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)
    payment_type: Mapped[str] = mapped_column(String(50), default=PaymentTypes.RECEIVE_PAYMENT.value)
    invoice_terms: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    
    # Foreign Keys
    shipping_address_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("address.id"), nullable=True)
    merchant_id: Mapped[int] = mapped_column(Integer, ForeignKey("merchants.id"))
    created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"))
    
    # Relationships - using string references to avoid circular imports
    split_config: Mapped[List["SplitPaymentRequests"]] = relationship(
        "SplitPaymentRequests", back_populates="payment_request"
    )

    shipping_address: Mapped[Optional["Address"]] = relationship("Address")

    recurring_config: Mapped[Optional["RecurringPaymentRequests"]] = relationship(
        "RecurringPaymentRequests", back_populates="payment_request"
    )

    line_items: Mapped[List["PaymentRequestLineItems"]] = relationship(
        "PaymentRequestLineItems", back_populates="payment_request"
    )

    products: Mapped[List["PaymentRequestProducts"]] = relationship(
        "PaymentRequestProducts", back_populates="payment_request"
    )

    payment_methods: Mapped[List["PaymentMethod"]] = relationship(
        "PaymentMethod",
        # back_populates="payment_request",
        primaryjoin="and_(PaymentRequest.id==PaymentMethod.payment_request_id, PaymentMethod.deleted_at == None)",
    )
    payment_request_intents: Mapped[List["PaymentRequestIntent"]] = relationship(
        "PaymentRequestIntent",
        primaryjoin="and_(PaymentRequest.id==PaymentRequestIntent.payment_request_id)",
    )

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

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

    payment_request_customers: Mapped[List["PaymentRequestCustomer"]] = relationship(
        "PaymentRequestCustomer", back_populates="payment_requests"
    )

    attachments: Mapped[List["File"]] = relationship("File", secondary=payment_request_attachments_map)

    notes: Mapped[List["Note"]] = relationship("Note", secondary=payment_request_notes_map)

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

    merchant: Mapped["Merchant"] = relationship("Merchant")

    created_by: Mapped["User"] = relationship("User")

    payment_request_adjustment: Mapped[List["PaymentRequestAdjustments"]] = relationship(
        "PaymentRequestAdjustments", back_populates="payment_request"
    )

    payment_request_authorizations: Mapped[List["PaymentRequestAuthorizations"]] = relationship(
        "PaymentRequestAuthorizations", back_populates="payment_request"
    )

    payment_request_approvals: Mapped[List["PaymentRequestApproval"]] = relationship(
        "PaymentRequestApproval", back_populates="payment_request"
    )

    @hybrid_property
    def is_authorized(self) -> bool:
        if self.authorization_type == PaymentAuthorizationTypes.PRE_AUTH.value:
            return True
        return True if self.payment_methods and len(self.payment_methods) > 0 else False

    @hybrid_property
    def can_edit(self) -> bool:
        if self.status in [
            PaymentRequestStatusTypes.PROCESSING.value,
            PaymentRequestStatusTypes.RETRYING.value,
            PaymentRequestStatusTypes.PAID.value,
            PaymentRequestStatusTypes.CANCELLED.value,
            PaymentRequestStatusTypes.EXPIRED.value,
        ]:
            return False
        return True

    @hybrid_property
    def days_until_due(self) -> int:
        if self.status == PaymentRequestStatusTypes.PAID.value:
            return 0
        if self.status == PaymentRequestStatusTypes.OVERDUE.value:
            if self.due_date:
                today_date = datetime.now().date()
                difference = self.due_date.date() - today_date
                return difference.days
            else:
                return 0
        return 0

    @hybrid_property
    def can_delete(self) -> bool:
        if self.status in [
            PaymentRequestStatusTypes.PAID.value,
            PaymentRequestStatusTypes.PARTIALLY_PAID.value,
            PaymentRequestStatusTypes.PROCESSING.value,
            PaymentRequestStatusTypes.RETRYING.value,
            PaymentRequestStatusTypes.OVERDUE.value,
            PaymentRequestStatusTypes.AUTHORISED.value,
        ]:
            return False
        return True

    @hybrid_property
    def status_text(self) -> str:
        if (
            self.status
            and self.status >= PaymentRequestStatusTypes.REFUNDED.value
            and hasattr(self, 'txn_status')
            and self.txn_status < (PaymentRequestStatusTypes.REFUNDED.value + 100)
        ):
            # return text 'refund' for all refund status types
            return "refund"
        return next(
            (
                " ".join(c.name.lower().split("_"))
                for c in PaymentRequestStatusTypes
                if c.value == self.status
            ),
            "unknown",  # Default fallback
        )

    @hybrid_property
    def tax_type_text(self) -> str:
        return next(
            (
                " ".join(c.name.lower().split("_"))
                for c in TaxType
                if c.value == self.tax_type
            ),
            TaxType.EXCLUSIVE.value,
        )

    @hybrid_property
    def has_remaining_payments(self) -> bool:
        if self.payment_frequency == PaymentFrequencies.ONE_TIME.value:
            is_paid: bool = self.status == PaymentRequestStatusTypes.PAID.value
            return not is_paid
        elif self.payment_frequency == PaymentFrequencies.SPLIT.value:
            return (
                len([split for split in self.split_config if split.paid_date is None])
                > 0
            )
        elif self.payment_frequency == PaymentFrequencies.RECURRING.value:
            is_subscription_ended: bool = True
            config = self.recurring_config
            if config:
                if config.end_type == RecurringPaymentEndTypes.NEVER.value:
                    is_subscription_ended = False
                elif (
                    config.end_type == RecurringPaymentEndTypes.DATE.value
                    and config.end_date
                    and config.end_date.replace(tzinfo=None) > datetime.now()
                ):
                    is_subscription_ended = False
                elif config.end_type == RecurringPaymentEndTypes.UNTIL_COUNT.value:
                    pay_until_count = config.pay_until_count or 0
                    if hasattr(config, 'prorate_first_payment') and config.prorate_first_payment and hasattr(config, 'prorate_date') and config.prorate_date:
                        pay_until_count += 1
                    if pay_until_count > (config.paid_count or 0):
                        is_subscription_ended = False
                elif (
                    config.end_type == RecurringPaymentEndTypes.UNTIL_CANCELLED.value
                    and self.status != PaymentRequestStatusTypes.CANCELLED.value
                ):
                    is_subscription_ended = False
            return not is_subscription_ended
        return False

    @hybrid_property
    def is_email_enabled(self) -> bool:
        """
        A property to check if email notifications are enabled for this payment request
        """
        if self.enable_email is not None:
            return self.enable_email
        # Simplified without settings dependency
        return True

    @hybrid_property
    def is_sms_enabled(self) -> bool:
        """
        A property to check if sms notifications are enabled for this payment request
        """
        if self.enable_sms is not None:
            return self.enable_sms
        # Simplified without settings dependency
        return False
