import base64
import warnings
from datetime import datetime, timedelta
from typing import Optional, List, Any, Dict, 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
