from typing import List
from sqlalchemy import (
    Boolean,
    DateTime,
    ForeignKey,
    String,
    Text,
    case,
    Float,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship, Session
from src.apps.payment_requests.models.payment_request import PaymentRequest
from src.core.database import SessionCelery
from datetime import datetime
from src.apps.payment_methods.models.payment_method_ach_details import PaymentMethodAchDetails
from src.apps.payment_methods.models.payment_method_card_details import PaymentMethodCardDetails
from src.apps.base.models.base import Base
from src.core.utils.enums import PaymentMethodScopes, PaymentMethodTypes
from sqlalchemy.sql import func
from sqlalchemy.orm import Query
from sqlalchemy.ext.hybrid import hybrid_property


class PaymentMethod(Base):
    """
    PaymentMethods Model: ORM class for PaymentMethods Entity
    """

    __tablename__ = "payment_methods"

    id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True)
    method: Mapped[str] = mapped_column(String(50), default=PaymentMethodTypes.CARD)
    payment_method_id: Mapped[str | None] = mapped_column(Text, nullable=True, unique=True)
    is_connected: Mapped[bool] = mapped_column(Boolean, default=False)
    scope: Mapped[str] = mapped_column(String(50), default=PaymentMethodScopes.MERCHANT)
    created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now())
    updated_at: Mapped[DateTime | None] = mapped_column(DateTime, nullable=True, onupdate=func.now())
    deleted_at: Mapped[DateTime | None] = mapped_column(DateTime, nullable=True)
    in_use: Mapped[bool] = mapped_column(Boolean, default=True)
    pay_split_type: Mapped[str | None] = mapped_column(String(20), nullable=True)
    pay_split_value: Mapped[float | None] = mapped_column(Float, nullable=True)
    paid_date: Mapped[DateTime | None] = mapped_column(DateTime, nullable=True)

    card_details_id: Mapped[int | None] = mapped_column(
        ForeignKey("payment_methods_card_details.id"), nullable=True
    )
    card_details: Mapped["PaymentMethodCardDetails"] = relationship("PaymentMethodCardDetails", lazy="joined")

    ach_details_id: Mapped[int | None] = mapped_column(
        ForeignKey("payment_methods_ach_details.id"), nullable=True
    )
    ach_details: Mapped["PaymentMethodAchDetails"] = relationship("PaymentMethodAchDetails", lazy="joined")

    cheque_details_id: Mapped[int | None] = mapped_column(
        ForeignKey("payment_methods_cheque_details.id"), nullable=True
    )
    cheque_details: Mapped["PaymentMethodChequeDetails"] = relationship("PaymentMethodChequeDetails")

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

    payer_id: Mapped[int] = mapped_column(ForeignKey("customer_contacts.id"))
    payer: Mapped["CustomerContact"] = relationship("CustomerContact", back_populates="payment_methods")

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

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

    billing_address_id: Mapped[int | None] = mapped_column(ForeignKey("address.id"), nullable=True)
    billing_address: Mapped["Address"] = relationship("Address", uselist=False)

    def __init__(self, **kwargs):
        """Initialize PaymentMethod with default values"""
        # Set default values if not provided
        kwargs.setdefault('method', PaymentMethodTypes.CARD)
        kwargs.setdefault('is_connected', False)
        kwargs.setdefault('scope', PaymentMethodScopes.MERCHANT)
        kwargs.setdefault('in_use', True)
        super().__init__(**kwargs)

    @hybrid_property
    def reference_id(self) -> str:
        if self.method == PaymentMethodTypes.CARD:
            return self.card_details.reference_id
        elif self.method == PaymentMethodTypes.ACH:
            return self.ach_details.reference_id
        return ""

    @reference_id.expression
    def reference_id(cls):
        return case(
            (
                cls.method == PaymentMethodTypes.ACH,
                PaymentMethodAchDetails.reference_id,
            ),
            (
                cls.method == PaymentMethodTypes.CARD,
                PaymentMethodCardDetails.reference_id,
            ),
            else_="N.A",
        )

    @hybrid_property
    def is_default(self) -> bool:
        if self.method == PaymentMethodTypes.CARD:
            return self.card_details.is_default
        elif self.method == PaymentMethodTypes.ACH:
            return self.ach_details.is_default
        return False

    @is_default.expression
    def is_default(cls):
        return case(
            (cls.method == PaymentMethodTypes.ACH, PaymentMethodAchDetails.is_default),
            (
                cls.method == PaymentMethodTypes.CARD,
                PaymentMethodCardDetails.is_default,
            ),
            else_=False,
        )

    @hybrid_property
    def is_autosaved(self) -> bool:
        if self.method == PaymentMethodTypes.CARD:
            return self.card_details.is_autosaved
        elif self.method == PaymentMethodTypes.ACH:
            return self.ach_details.is_autosaved
        return False

    @is_autosaved.expression
    def is_autosaved(cls):
        return case(
            (
                cls.method == PaymentMethodTypes.ACH,
                PaymentMethodAchDetails.is_autosaved,
            ),
            (
                cls.method == PaymentMethodTypes.CARD,
                PaymentMethodCardDetails.is_autosaved,
            ),
            else_=False,
        )

    @hybrid_property
    def is_deletable(
        self,
    ) -> bool:
        if self.is_default:
            return False
        status: bool = True
        with SessionCelery() as db:
            db_query: Query = db.query(PaymentMethod).filter(
                PaymentMethod.deleted_at.is_(None)
            )
            if self.card_details_id:
                db_query = db_query.filter(
                    PaymentMethod.card_details_id == self.card_details_id
                )
            elif self.ach_details_id:
                db_query = db_query.filter(
                    PaymentMethod.ach_details_id == self.ach_details_id
                )
            all_pms = db_query.all()
            all_prs: List[PaymentRequest] = [
                method.payment_request for method in all_pms
            ]
            for pr in all_prs:
                if pr and pr.has_remaining_payments:
                    status = False
                    break
        return status

    @hybrid_property
    def last_used_at(self) -> datetime:
        if self.method == PaymentMethodTypes.CARD:
            return self.card_details.last_used_at
        elif self.method == PaymentMethodTypes.ACH:
            return self.ach_details.last_used_at
        return None
