"""
Payment Request request schemas.
"""

import base64
import pytz
from typing import List, Optional
from datetime import datetime, timezone
from dateutil.relativedelta import relativedelta
from pydantic import BaseModel, Field, field_validator, model_validator

from src.apps.base.utils import regexp
from src.apps.payment_requests.enums import (
    PaymentCurrencies,
    PaymentFrequencies,
    PaymentAuthorizationTypes,
    SurchargeTypes,
    TaxType,
    PaymentRequestStatusTypes,
    SplitPaymentTypes,
    DiscountTypes,
    RecurringPaymentEndTypes,
    RecurringPaymentRepeatTypes,
    RecurringPaymentIntervals
)
from src.apps.base.schemas.common import BaseSchema, AddressCreateRequestSchema
from src.core.utils.constants import MAX_UPLOAD_LIMIT

class PaymentRequestBase(BaseSchema):
    """Base payment request schema"""
    reference: Optional[str] = Field(default=None, description="Reference number for the payment")
    save_payment_method: Optional[bool] = Field(default=False, description="Save payment method for future use")
    allow_tip: Optional[bool] = Field(default=False, description="Allow tips on this payment")
    require_billing_address: Optional[bool] = Field(default=False, description="Require billing address")
    require_sms_authorization: Optional[bool] = Field(default=False, description="Require SMS authorization")
    require_shipping_address: Optional[bool] = Field(default=False, description="Require shipping address")
    require_signature_authorization: Optional[bool] = Field(default=False, description="Require signature authorization")
    require_cvv: Optional[bool] = Field(default=False, description="Require CVV verification")
    is_certified: Optional[bool] = Field(default=False, description="Is payment certified")
    tax_type: Optional[TaxType] = Field(default=TaxType.NOTAX, description="Tax type for the payment")
    configure_adjustment: Optional[bool] = Field(default=False, description="Configure payment adjustment")
    message: Optional[str] = Field(default="", description="Message for the payment request")
    description: Optional[str] = Field(default=None, description="Description of payment request")
    tax_percent: Optional[float] = Field(default=0, description="Tax percentage")


class PaymentSplitTenderSchema(BaseSchema):
    """Payment split tender schema"""
    payment_method: str = Field(description="Payment method for split tender")
    amount: float = Field(description="Amount for this split")


class PartialPaymentRequestConfigCreateSchema(BaseSchema):
    """Partial payment configuration schema"""
    partial_payment_type: SplitPaymentTypes = Field(description="Type of partial payment")
    partial_payment_value: float = Field(description="Value for partial payment")
    due_date: datetime = Field(description="Due date for partial payment")


class PaymentRequestMethodSchema(BaseSchema):
    """Payment request method schema"""
    payment_method: str = Field(description="Payment method type")
    is_default: Optional[bool] = Field(default=False, description="Is default payment method")


class PaymentRequestAdjustmentsCreate(BaseSchema):
    """Payment request adjustments schema"""
    is_discounted: Optional[bool] = Field(default=False, description="Has discount")
    discount_type: Optional[DiscountTypes] = Field(description="Type of discount")
    discount_amount: Optional[float] = Field(default=0, description="Discount amount")
    is_surcharged: Optional[bool] = Field(default=False, description="Has surcharge")
    surcharge_amount: Optional[float] = Field(default=0, description="Surcharge amount")


class PaymentRequestLineItemCreate(BaseSchema):
    """Payment request line item schema"""
    title: str = Field(description="Title of line item")
    description: Optional[str] = Field(default="", description="Description of line item")
    unit_price: float = Field(description="Unit price of item")
    quantity: int = Field(description="Quantity of items")
    tax: Optional[float] = Field(default=0, description="Tax for item")
    tax_id: Optional[int] = Field(default=None, description="Tax ID reference")
    product_id: Optional[int] = Field(default=None, description="Product ID reference")
    cost: Optional[float] = Field(default=0, description="Cost of item")
    upcharge: Optional[float] = Field(default=0, description="Upcharge amount")
    discount: Optional[float] = Field(default=0, description="Discount amount")
    discount_type: Optional[DiscountTypes] = Field(description="Type of discount")
    tax_amount: Optional[float] = Field(default=0, description="Calculated tax amount")


class PaymentRequestProductCreate(BaseSchema):
    """Payment request product schema"""
    product_slug: Optional[str] = Field(default=None, description="Product slug")
    unit_price: Optional[float] = Field(default=0, description="Unit price override")
    quantity: int = Field(description="Quantity of products")
    tax_percent: Optional[float] = Field(default=None, description="Tax percentage override")
    is_taxable: Optional[bool] = Field(default=False, description="Is product taxable")


class SplitPaymentRequestCreate(BaseSchema):
    """Split payment request configuration schema"""
    sequence: int = Field(description="Sequence number of split")
    split_type: SplitPaymentTypes = Field(description="Type of split (percentage/amount)")
    split_value: float = Field(description="Value of split")
    billing_date: datetime = Field(description="Billing date for split")
    due_date: datetime = Field(description="Due date for split")


class RecurringPaymentRequestCreate(BaseSchema):
    """Recurring payment request configuration schema"""
    prorate_first_payment: Optional[bool] = Field(default=False, description="Prorate first payment")
    prorate_date: Optional[datetime] = Field(default=None, description="Prorate date")
    start_date: datetime = Field(description="Start date for recurring payments")
    repeat_type: RecurringPaymentRepeatTypes = Field(description="Repeat type")
    interval: RecurringPaymentIntervals = Field(description="Interval type")
    interval_value: Optional[int] = Field(default=1, description="Interval value")
    end_type: RecurringPaymentEndTypes = Field(description="End type")
    end_date: Optional[datetime] = Field(default=None, description="End date")
    pay_until_count: Optional[int] = Field(default=None, description="Pay until count")


class MerchantPaymentRequestCreateSchema(PaymentRequestBase):
    """Schema for creating merchant payment requests"""
    
    is_draft: bool = Field(default=False, description="Save this payment request as draft?")
    amount: float = Field(description="Amount of Payment Request, accepts decimal input")
    currency: PaymentCurrencies = Field(description="Three-letter ISO currency code, in lowercase. Eg: usd, eur, etc.")
    customer_id: str = Field(description="Unique ID (cus_xxx) of customer for whom this payment request is to be created")
    payer_id: str = Field(description="ID of payer for whom this payment request is to be created")
    payment_frequency: PaymentFrequencies = Field(description="Frequency of payment, (Split / Recurring / One-time)")
    authorization_type: Optional[PaymentAuthorizationTypes] = Field(description="PaymentRequest authorisation type, (Pre-Authorized / Request-Authorization)")
    payment_redirect_url: Optional[str] = Field(default=None, description="Redirection url of the payment")
    payment_split_tenders: Optional[List[PaymentSplitTenderSchema]] = Field(None,description="Split Tender details of the single type Payment")
    partial_payment: Optional[PartialPaymentRequestConfigCreateSchema] = Field(None,description="Partial payment details of the one time payment")
    payment_methods: Optional[List[PaymentRequestMethodSchema]] = Field(None,description="Payment Methods Requests")
    payment_adjustments: Optional[PaymentRequestAdjustmentsCreate] = Field(None,description="Payment Adjustment Details which consist of surcharge/Discounts")
    payment_split_frequency: Optional[str] = Field(None,description="Frequency of payment split, (Weekly / Monthly / Quarterly / Yearly)")
    due_date: Optional[datetime] = Field(None,description="Due date and time of payment as unix timestamp")
    shipping_address_id: Optional[int] = Field(None,description="Shipping Address id for the payment")
    billing_date: Optional[datetime] = Field(None,description="Billing date and time of payment as unix timestamp")
    line_items: Optional[List[PaymentRequestLineItemCreate]] = Field(None,description="Any transactional items included in the payment request")
    billed_products: Optional[List[PaymentRequestProductCreate]] = Field(None,description="Billed products in this payment request")
    split_config: Optional[List[SplitPaymentRequestCreate]] = Field(None,description="Configuration for split payment request")
    recurring_config: Optional[RecurringPaymentRequestCreate] = Field(None,description="Configuration for recurring payment request")
    attachments: Optional[List[int]] = Field(None,description="Uploaded file ids of optional attachments")
    shipping_fee: Optional[float] = Field(ge=0, le=99999, description="Shipping fees for the billable items.")
    enable_email: Optional[bool] = Field(default=False,description="Enable Email notifications for this payment request?")
    enable_sms: Optional[bool] = Field(default=False,description="Enable SMS notifications for this payment request?")
    billing_address: Optional[AddressCreateRequestSchema] = Field(None,description="Billing address that will be attached to the payment method")
    approver_id: Optional[str] = Field(None, description="ID of approver for whom this payment request is to be created")
    payer_email_request_enabled: Optional[bool] = Field(None,description="Receive request email for the payment for payer")
    payer_email_receipt_enabled: Optional[bool] = Field(None,description="Receive receipt email for the payment for payer")
    payer_sms_request_enabled: Optional[bool] = Field(None,description="Receive request sms for the payment for payer")
    payer_sms_receipt_enabled: Optional[bool] = Field(None,description="Receive receipt sms for the payment for payer")
    approver_email_receipt_enabled: Optional[bool] = Field(None,description="Receive receipt email for the payment for approver")
    approver_sms_receipt_enabled: Optional[bool] = Field(None,description="Receive receipt sms for the payment for approver")
    enable_email_receipt: Optional[bool] = Field(None,description="Enable Email notifications for this payment request?")
    enable_sms_receipt: Optional[bool] = Field(None,description="Enable SMS notifications for this payment request?")
    is_surcharge_enabled: Optional[bool] = Field(None,description="Surcharge enabled for cost adjustment")
    surcharge_type: Optional[SurchargeTypes] = Field(None,description="Surcharge type inclusive/exclusive")
    invoice_terms: Optional[str] = Field(None,description="Terms and conditions for the invoice")

    @field_validator("due_date")
    @classmethod
    def validate_due_date(cls, v):
        """Validate due date"""
        if v is not None:
            return validate_payment_due_date(v, "due_date")
        return v

    @field_validator("split_config")
    @classmethod
    def validate_split_config(cls, v):
        """Validate split configuration"""
        errors: List[ValueError] = []
        if v is not None:
            if len(v) < 2:
                raise ValueError("At least 2 splits are required")
            for split in v:
                try:
                    split.due_date = validate_payment_due_date(split.due_date, "split_config.due_date")
                    split.billing_date = validate_payment_due_date(split.billing_date, "split_config.billing_date")
                except ValueError as e:
                    errors.append(e)
                    break
            if len(errors) > 0:
                raise errors[0]
        return v

    @field_validator("recurring_config")
    @classmethod
    def validate_recurring_config(cls, v):
        """Validate recurring configuration"""
        if v is not None:
            v.start_date = validate_payment_due_date(v.start_date, "recurring_config.start_date")
            if v.prorate_first_payment:
                v.prorate_date = validate_payment_due_date(v.prorate_date, "recurring_config.prorate_date")
            if v.end_date:
                v.end_date = validate_payment_due_date(v.end_date, "recurring_config.end_date")
                if v.end_date.date() <= v.start_date.date():
                    raise ValueError("recurring_config.end_date must be greater than recurring_config.start_date")
        return v

    @field_validator("line_items")
    @classmethod
    def validate_line_items(cls, v):
        """Validate line items"""
        return v

    @field_validator("attachments")
    @classmethod
    def validate_attachments(cls, v):
        """Validate attachments"""
        if v is not None:
            if len(v) > MAX_UPLOAD_LIMIT:
                raise ValueError(f"A maximum of {MAX_UPLOAD_LIMIT} attachments can be included")
            
            # TODO: Validate attachment existence in database
            # This validation should check if files exist in the database
        return v

    @model_validator(mode='before')
    @classmethod
    def validate_payment_config(cls, values):
        """Validate payment configuration based on frequency"""
        payment_frequency = values.get("payment_frequency", None)
        if payment_frequency == PaymentFrequencies.ONE_TIME:
            if not values.get("due_date", False):
                raise ValueError("due_date is required")
        elif payment_frequency == PaymentFrequencies.SPLIT:
            if not values.get("split_config", False):
                raise ValueError("split_config is required")
        elif payment_frequency == PaymentFrequencies.RECURRING:
            if not values.get("recurring_config", False):
                raise ValueError("recurring_config is required")
        return values

    @model_validator(mode='before')
    @classmethod
    def validate_authorization_type(cls, values):
        """Validate authorization type"""
        is_draft = values.get("is_draft", False)
        if not is_draft and values.get("authorization_type", None) is None:
            raise ValueError("Please provide an Authorization Type")
        return values

    @model_validator(mode='before')
    @classmethod
    def validate_notifications(cls, values):
        """Validate notification settings"""
        is_draft = values.get("is_draft", False)
        auth_mode = values.get("authorization_type")
        if not is_draft:
            if auth_mode == PaymentAuthorizationTypes.REQUEST_AUTH:
                if not values.get("enable_email", False):
                    raise ValueError("Please enable email notifications for request authorization payments")
        return values

    @model_validator(mode='before')
    @classmethod
    def validate_transaction_amount(cls, values):
        """Validate transaction amounts"""
        transaction_amount = values.get("amount", 0)
        payment_frequency = values.get("payment_frequency", None)
        errors: List[ValueError] = []
        
        if payment_frequency == PaymentFrequencies.SPLIT:
            splits = values.get("split_config", [])
            for s in splits:
                if s["split_type"] == SplitPaymentTypes.AMOUNT:
                    if s["split_value"] < 100:
                        errors.append(ValueError("Amount must be greater than $1"))
                        break
                    elif s["split_value"] >= transaction_amount:
                        errors.append(ValueError("Amount must be lesser than payment amount"))
                        break
                elif s["split_type"] == SplitPaymentTypes.PERCENTAGE:
                    if s["split_value"] < 1:
                        errors.append(ValueError("Split Percentage must be greater than or equal to 1"))
                        break
                    if s["split_value"] > 99:
                        errors.append(ValueError("Split Percentage must be lesser than or equal to 99"))
                        break

                split_amt = calculate_split_amount(SplitPaymentRequestCreate(**s), transaction_amount)
                if split_amt < 100:
                    errors.append(ValueError(f"Split amount for sequence {s['sequence']} must be greater than $1"))
                    break
                if split_amt > 99999999:
                    errors.append(ValueError(f"Split amount for sequence {s['sequence']} must be lesser than $1000000"))
                    break
            if len(errors) > 0:
                raise errors[0]
                
        elif payment_frequency == PaymentFrequencies.RECURRING:
            # TODO: Add recurring payment validation logic
            pass

        if payment_frequency not in [PaymentFrequencies.SPLIT]:
            if transaction_amount < 100:
                raise ValueError("Amount must be greater than $1")
            elif transaction_amount > 99999999:
                raise ValueError("Amount must be lesser than $1000000")
        return values

    @model_validator(mode='before')
    @classmethod
    def validate_products_amount(cls, values):
        """Validate product amounts and calculate totals"""
        billed_products = values.get("billed_products", [])
        shipping_fee = round_half_up(values.get("shipping_fee", 0))
        payment_frequency = values.get("payment_frequency", None)
        transaction_amt: float = 0
        
        if len(billed_products) > 0:
            # TODO: Validate products exist and calculate amounts
            # This should validate products in database and calculate proper amounts
            pass
        return values

    @model_validator(mode='before')
    @classmethod
    def validate_tax_type(cls, val):
        """Validate tax type"""
        line_item = val.get("line_items", [])
        if len(line_item) == 0:
            val["tax_type"] = TaxType.NOTAX
        return val

    class Config:
        """Schema configuration"""
        json_schema_extra = {
            "example": {
                "customer_id": "cus_asbbeqwb123123",
                "payment_frequency": "split",
                "authorization_type": "request_auth",
                "payment_method": "card",
                "save_payment_method": True,
                "payment_method_token": "pm_asbbeqwb123123",
                "amount": 100.00,
                "currency": "usd",
                "line_items": [
                    {
                        "title": "Horlicks",
                        "description": "Taste for good",
                        "unit_price": 100,
                        "quantity": 1,
                    }
                ],
                "split_config": [
                    {
                        "sequence": 1,
                        "split_type": "percentage",
                        "split_value": 50,
                        "billing_date": "2021-10-18T12:33:28.146250+00:00",
                        "due_date": "2021-10-18T12:33:28.146250+00:00",
                    },
                    {
                        "sequence": 2,
                        "split_type": "percentage",
                        "split_value": 50,
                        "billing_date": "2021-10-18T12:33:28.146250+00:00",
                        "due_date": "2021-10-18T12:33:28.146250+00:00",
                    },
                ],
                "recurring_config": {
                    "prorate_first_payment": False,
                    "start_date": "2022-01-19T12:33:28.146250+00:00",
                    "repeat_type": "on_date",
                    "interval": "month",
                    "interval_value": 12,
                    "end_type": "date",
                    "end_date": "2023-01-11T12:33:28.146250+00:00",
                },
            }
        }


def validate_payment_due_date(val: datetime, field: str) -> datetime:
    """
    Validate payment due date
    
    Args:
        val: The datetime to validate
        field: The field name for error messages
        
    Returns:
        datetime: Validated datetime
        
    Raises:
        ValueError: If date is invalid
    """
    utc_today: datetime = datetime.now(timezone.utc).replace(
        hour=0, minute=0, second=0, microsecond=0
    )
    payment_due_date: datetime = datetime.now(timezone.utc)
    try:
        payment_due_date = datetime.fromtimestamp(
            val.timestamp(), tz=timezone.utc
        )  # incoming date is always UTC
    except Exception as e:
        raise ValueError(f"Please provide a valid {field} for the payment")

    if payment_due_date < utc_today:
        max_utc_offset = 15
        max_utc_offset_dt = utc_today - relativedelta(hours=+int(max_utc_offset))
        if payment_due_date < max_utc_offset_dt:
            raise ValueError(f"{field} must be greater than or equal to today's date")

    return payment_due_date


def calculate_split_amount(split: SplitPaymentRequestCreate, total_amount: float) -> float:
    """
    Calculate split amount based on type and value
    
    Args:
        split: Split configuration
        total_amount: Total payment amount
        
    Returns:
        float: Calculated split amount
    """
    if split.split_type == SplitPaymentTypes.PERCENTAGE:
        return round_half_up((split.split_value / 100) * total_amount)
    elif split.split_type == SplitPaymentTypes.AMOUNT:
        return round_half_up(split.split_value)
    return 0.0


def round_half_up(number, decimals: int = 0) -> float:
    """
    This function rounds a number half up. Eg. 18.5 will round to 19
    
    Args:
        number: Number to round
        decimals: Number of decimal places
        
    Returns:
        float: Rounded number
    """
    rounded_value = int(number * (10**decimals) + 0.5) / (10**decimals)
    if rounded_value % 1 == 0:
        rounded_value = int(rounded_value)
    return rounded_value