import http
import os
from contextlib import suppress
from time import sleep
from typing import Optional
from urllib.parse import urljoin

import requests
from django.conf import settings
from django.core.files.base import ContentFile

from store.utils import is_migration_running

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'poizonstore.settings')


class CDEKStatus:
    # Принят
    ACCEPTED = "ACCEPTED"
    # Создан
    CREATED = "CREATED"
    # Принят на склад отправителя
    RECEIVED_AT_SHIPMENT_WAREHOUSE = "RECEIVED_AT_SHIPMENT_WAREHOUSE"
    # Выдан на отправку в г. отправителе
    READY_FOR_SHIPMENT_IN_SENDER_CITY = "READY_FOR_SHIPMENT_IN_SENDER_CITY"
    # Возвращен на склад отправителя
    RETURNED_TO_SENDER_CITY_WAREHOUSE = "RETURNED_TO_SENDER_CITY_WAREHOUSE"
    # Сдан перевозчику в г. отправителе
    TAKEN_BY_TRANSPORTER_FROM_SENDER_CITY = "TAKEN_BY_TRANSPORTER_FROM_SENDER_CITY"
    # Отправлен в г. транзит
    SENT_TO_TRANSIT_CITY = "SENT_TO_TRANSIT_CITY"
    # Встречен в г. транзите
    ACCEPTED_IN_TRANSIT_CITY = "ACCEPTED_IN_TRANSIT_CITY"
    # Принят на склад транзита
    ACCEPTED_AT_TRANSIT_WAREHOUSE = "ACCEPTED_AT_TRANSIT_WAREHOUSE"
    # Возвращен на склад транзита
    RETURNED_TO_TRANSIT_WAREHOUSE = "RETURNED_TO_TRANSIT_WAREHOUSE"
    # Выдан на отправку в г. транзите
    READY_FOR_SHIPMENT_IN_TRANSIT_CITY = "READY_FOR_SHIPMENT_IN_TRANSIT_CITY"
    # Сдан перевозчику в г. транзите
    TAKEN_BY_TRANSPORTER_FROM_TRANSIT_CITY = "TAKEN_BY_TRANSPORTER_FROM_TRANSIT_CITY"
    # Отправлен в г. отправитель
    SENT_TO_SENDER_CITY = "SENT_TO_SENDER_CITY"
    # Отправлен в г. получатель
    SENT_TO_RECIPIENT_CITY = "SENT_TO_RECIPIENT_CITY"
    # Встречен в г. отправителе
    ACCEPTED_IN_SENDER_CITY = "ACCEPTED_IN_SENDER_CITY"
    # Встречен в г. получателе
    ACCEPTED_IN_RECIPIENT_CITY = "ACCEPTED_IN_RECIPIENT_CITY"
    # Принят на склад доставки
    ACCEPTED_AT_RECIPIENT_CITY_WAREHOUSE = "ACCEPTED_AT_RECIPIENT_CITY_WAREHOUSE"
    # Принят на склад до востребования
    ACCEPTED_AT_PICK_UP_POINT = "ACCEPTED_AT_PICK_UP_POINT"
    # Выдан на доставку
    TAKEN_BY_COURIER = "TAKEN_BY_COURIER"
    # Возвращен на склад доставки
    RETURNED_TO_RECIPIENT_CITY_WAREHOUSE = "RETURNED_TO_RECIPIENT_CITY_WAREHOUSE"
    # Вручен
    DELIVERED = "DELIVERED"
    # Не вручен
    NOT_DELIVERED = "NOT_DELIVERED"
    # Некорректный заказ
    INVALID = "INVALID"
    # Таможенное оформление в стране отправления
    IN_CUSTOMS_INTERNATIONAL = "IN_CUSTOMS_INTERNATIONAL"
    # Отправлено в страну назначения
    SHIPPED_TO_DESTINATION = "SHIPPED_TO_DESTINATION"
    # Передано транзитному перевозчику
    PASSED_TO_TRANSIT_CARRIER = "PASSED_TO_TRANSIT_CARRIER"
    # Таможенное оформление в стране назначения
    IN_CUSTOMS_LOCAL = "IN_CUSTOMS_LOCAL"
    # Таможенное оформление завершено
    CUSTOMS_COMPLETE = "CUSTOMS_COMPLETE"
    # Заложен в постамат
    POSTOMAT_POSTED = "POSTOMAT_POSTED"
    # Изъят из постамата курьером
    POSTOMAT_SEIZED = "POSTOMAT_SEIZED"
    # Изъят из постамата клиентом
    POSTOMAT_RECEIVED = "POSTOMAT_RECEIVED"


class CDEKClient:
    AUTH_ENDPOINT = 'oauth/token'
    ORDER_INFO_ENDPOINT = 'orders'
    CALCULATOR_TARIFF_ENDPOINT = 'calculator/tariff'
    CALCULATOR_TARIFF_LIST_ENDPOINT = 'calculator/tarifflist'
    BARCODE_ENDPOINT = 'print/barcodes'

    MAX_RETRIES = 2

    def __init__(self, client_id, client_secret, grant_type='client_credentials'):
        self.api_url = 'https://api.cdek.ru/v2/'
        self.client_id = client_id
        self.client_secret = client_secret
        self.grant_type = grant_type

        self.session = requests.Session()

    def request(self, method, url, *args, **kwargs):
        joined_url = urljoin(self.api_url, url)
        request = requests.Request(method, joined_url, *args, **kwargs)

        retries = 0
        while retries < self.MAX_RETRIES:
            retries += 1
            prepared = self.session.prepare_request(request)
            try:
                r = self.session.send(prepared, timeout=settings.EXTERNAL_API_TIMEOUT_SEC)
            except:
                continue

            # TODO: handle/log errors
            if r.status_code == http.HTTPStatus.UNAUTHORIZED:
                self.authorize()
                continue
            return r

    def authorize(self):
        params = {
            'client_id': self.client_id,
            'client_secret': self.client_secret,
            'grant_type': self.grant_type
        }
        r = self.request('POST', self.AUTH_ENDPOINT, params=params)
        if r:
            data = r.json()
            token = data['access_token']
            self.session.headers.update({'Authorization': f'Bearer {token}'})

    def get_order_info(self, im_number):
        params = {
            'im_number': str(im_number)
        }
        return self.request('GET', self.ORDER_INFO_ENDPOINT, params=params)

    def create_order(self, order_data):
        return self.request('POST', self.ORDER_INFO_ENDPOINT, json=order_data)

    def edit_order(self, order_data):
        return self.request('PATCH', self.ORDER_INFO_ENDPOINT, json=order_data)

    def calculate_tariff(self, data):
        return self.request('POST', self.CALCULATOR_TARIFF_ENDPOINT, json=data)

    def calculate_tarifflist(self, data):
        return self.request('POST', self.CALCULATOR_TARIFF_LIST_ENDPOINT, json=data)

    def generate_barcode(self, cdek_number, format="A6") -> Optional[str]:
        request_data = {
            "orders": [{"cdek_number": cdek_number}],
            "copy_count": 1,
            "format": format
        }

        r = self.request('POST', self.BARCODE_ENDPOINT, json=request_data)
        if not r:
            return None

        resp_data = r.json()
        if 'entity' not in resp_data:
            return None

        barcode_uuid = resp_data['entity']['uuid']
        return barcode_uuid

    def get_barcode_url(self, uuid) -> Optional[str]:
        if not uuid:
            return None

        r = self.request('GET', f'{self.BARCODE_ENDPOINT}/{uuid}')
        if not r:
            return None

        resp_data = r.json()
        if 'entity' not in resp_data:
            return None

        url = resp_data['entity'].get('url')
        return url

    def get_barcode_file(self, cdek_number):
        uuid = self.generate_barcode(cdek_number)
        sleep(2)  # Sometimes url are not yet created, so be prepared for this
        url = self.get_barcode_url(uuid)
        if not url:
            return None

        r = self.request('GET', url)
        return ContentFile(r.content) if r and r.content else None

    def get_order_statuses(self, cdek_number):
        params = {
            'cdek_number': str(cdek_number)
        }

        r = self.request('GET', self.ORDER_INFO_ENDPOINT, params=params)
        if not r:
            return []

        with suppress(KeyError):
            statuses = r.json()['entity']['statuses']
            statuses = [s.get('code') for s in statuses]
            return statuses

        return []


client = CDEKClient(settings.CDEK_CLIENT_ID, settings.CDEK_CLIENT_SECRET)

if not is_migration_running():
    client.authorize()
