import base64
import warnings
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Union

from pydantic import BaseModel, Field, root_validator

from .. import __version__ as sdk_version
from ..enums import Countries, Currencies
from ..models import Payer
from ..utils import convert_to_iso8601


class M3Headers(BaseModel):
    """This headers are modeled based on this documentation
    https://payjoy.atlassian.net/wiki/spaces/ENGINEERIN/pages/2690619670541/Finance+Engine+API+Webhook+-+Successful+payment+notification
    """

    content_type: str = Field(alias="Content-Type", default="application/json")
    user_agent: str = Field(alias="User-Agent", default=f"Payjoy D-Local SDK / {sdk_version}")
    authorization: Optional[str] = Field(alias="Authorization")

    def set_authorization(self, username: str, password: str, encoding: str = "utf-8") -> str:
        base64_encoded: str = base64.b64encode(f"{username}:{password}".encode(encoding)).decode(encoding)
        self.authorization = f"Basic {base64_encoded}"
        return self.authorization


class M3Payment(BaseModel):
    """Payload content for Payment resolution after D-Local calls the notification_url."""

    order_id: str = Field(..., alias="paymentId")
    finance_engine_version: str = Field(..., alias="financeEngineVersion")
    credit_line_id: str = Field(..., alias="creditLineId")
    payment_provider: str = Field(..., alias="paymentProvider")
    country: Countries
    currency: Currencies
    amount: float
    timestamp_paid: str = Field(..., alias="timestampPaid")
    device_id: str = Field(..., alias="deviceId")
    user_id: str = Field(..., alias="userId")
    merchant_id: str = Field(..., alias="merchantId")
    clerk_id: str = Field(..., alias="clerkId")
    final_payment_info: Any = Field(..., alias="finalPaymentInfo")

    class Config:
        allow_population_by_field_name = True


class M3PaymentResolutionRequest(BaseModel):
    payments: List[M3Payment]

    class Config:
        allow_population_by_field_name = True


class M3PaymentResolutionResponse(BaseModel):
    valid: bool
    message: str


class M3PaymentRequest(BaseModel):
    """Payload sent to Payment's microservice from M3 to be sent to D-Local
    M3 ----M3PaymentRequest ----> Payments
    M3 <---M3PaymentResponse ---- Payments
    """

    amount: Union[int, float] = Field(description="Amount paid")
    payer: Payer = Field(description="Payer for the payment.")
    credit_line_id: str = Field(description="Unique identifier for a credit line or a finance order.")
    finance_engine_version: str = Field(description="Finance engine version FEv1 or FEv2")
    country: Countries = Field(description="Two letter country code of the country the device was sold in.")
    currency: Currencies = Field(description="The three letter currency code abbreviation.")
    payment_provider: str = Field(description="Enumeration value for the payment provider, ie D_LOCAL.")
    device_id: str = Field(description="This will contain the foreign key to the Devices table")
    user_id: str = Field(description="Foreign key to the Users table in M2 or the Persons table in M3")
    merchant_id: str = Field(
        description="Reference to the Merchants table. " "ONLY FEv2 needs to publish this information"
    )
    clerk_id: str = Field(description="Foreign key to the clerk who sold the device")

    def build_payment_resolution_request(self, timestamp_paid: str, order_id: str) -> M3Payment:
        m3_payment_dict = self.dict()
        m3_payment_dict["timestamp_paid"] = timestamp_paid
        m3_payment_dict["final_payment_info"] = None
        m3_payment_dict["order_id"] = order_id

        m3_payment_request = M3Payment(**m3_payment_dict)
        return m3_payment_request


class M3PaymentResponse(BaseModel):
    """Response from Payments to M3 after D-Local responded
    M3 ----M3PaymentRequest ----> Payments
    M3 <---M3PaymentResponse ---- Payments
    """

    order_id: str = Field(description="Unique UUID generated by the Payment's microservice.")
    redirect_url: str = Field(description="Url to redirect reported by D-Local")
    status: str = Field(description="Status of the payment reported by D-Local")
    amount: float = Field(description="Amount paid")
    payments_created: datetime = Field(
        description="Date and time the payment was " "created in the Payment's microservice."
    )
    date_received: datetime = Field(description="Date and time reported by D-Local as created.")
    expiration_time: Optional[datetime] = Field(description="Date and time that the ticket expires.")

    # _expiration_delta: Optional[timedelta] = PrivateAttr(default=timedelta(days=5))

    class Config:
        json_encoders = {datetime: lambda x: convert_to_iso8601(x)}

    @root_validator
    def compute_expiration_time(cls, values) -> Dict:
        """This is not the intended use of the @root_validator, but it was the cleanest way to include the
        expiration_time in the dictionary payload."""
        # TODO use _expiration_delta instead of hard coding 5 days.
        new_expiration_time = values["date_received"] + timedelta(days=5)
        if values["expiration_time"] is not None:
            warnings.warn(f"Overriding expiration_time with {new_expiration_time}")
        values["expiration_time"] = new_expiration_time
        return values
