# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

from decimal import Decimal
from itertools import zip_longest
from math import ceil

from trytond.i18n import gettext
from trytond.model import fields
from trytond.model.exceptions import AccessError
from trytond.modules.stock_package_shipping.stock import address_name
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
from trytond.wizard import StateAction, StateTransition, Wizard


class Package(metaclass=PoolMeta):
    __name__ = 'stock.package'

    sendcloud_shipping_id = fields.Integer("ID", readonly=True)
    sendcloud_shipping_tracking_url = fields.Char(
        "Tracking URL", readonly=True)

    def get_shipping_tracking_url(self, name):
        url = super().get_shipping_tracking_url(name)
        if (self.shipping_reference
                and self.shipment
                and self.shipment.id >= 0
                and self.shipment.carrier
                and self.shipment.carrier.shipping_service == 'sendcloud'):
            url = self.sendcloud_shipping_tracking_url
        return url

    @classmethod
    def copy(cls, packages, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('sendcloud_shipping_id')
        default.setdefault('sendcloud_shipping_tracking_url')
        return super().copy(packages, default=default)


class ShippingSendcloudMixin:
    __slots__ = ()

    def get_sendcloud_credential(self):
        pool = Pool()
        SendcloudCredential = pool.get('carrier.credential.sendcloud')

        pattern = self._get_sendcloud_credential_pattern()
        for credential in SendcloudCredential.search([]):
            if credential.match(pattern):
                return credential

    def _get_sendcloud_credential_pattern(self):
        return {
            'company': self.company.id,
            }

    def validate_packing_sendcloud(self):
        pass


class ShipmentOut(ShippingSendcloudMixin, metaclass=PoolMeta):
    __name__ = 'stock.shipment.out'


class ShipmentInReturn(ShippingSendcloudMixin, metaclass=PoolMeta):
    __name__ = 'stock.shipment.in.return'


class CreateShipping(metaclass=PoolMeta):
    __name__ = 'stock.shipment.create_shipping'

    sendcloud = StateAction(
        'stock_package_shipping_sendcloud.act_create_shipping_wizard')

    def transition_start(self):
        next_state = super().transition_start()
        if self.record.carrier.shipping_service == 'sendcloud':
            next_state = 'sendcloud'
        return next_state

    def do_sendcloud(self, action):
        ctx = Transaction().context
        return action, {
            'model': ctx['active_model'],
            'id': ctx['active_id'],
            'ids': [ctx['active_id']],
            }


class CreateShippingSendcloud(Wizard):
    __name__ = 'stock.shipment.create_shipping.sendcloud'

    start = StateTransition()

    def transition_start(self):
        pool = Pool()
        Package = pool.get('stock.package')

        shipment = self.record
        if shipment.shipping_reference:
            raise AccessError(
                gettext('stock_package_shipping_sendcloud'
                    '.msg_shipment_has_shipping_reference_number',
                    shipment=shipment.rec_name))

        credential = shipment.get_sendcloud_credential()
        carrier = shipment.carrier
        packages = shipment.root_packages

        parcels = []
        for package in packages:
            parcels.append(self.get_parcel(shipment, package, credential))
        parcels = credential.create_parcels(parcels)

        for package, parcel in zip_longest(packages, parcels):
            format_ = shipment.carrier.sendcloud_format.split()
            label_url = parcel['label']
            for key in format_:
                try:
                    index = int(key)
                except ValueError:
                    key += '_printer'
                    label_url = label_url[key]
                else:
                    label_url = label_url[index]
            package.sendcloud_shipping_id = parcel['id']
            package.shipping_label = credential.get_label(label_url)
            package.shipping_label_mimetype = carrier.shipping_label_mimetype
            package.shipping_reference = parcel['tracking_number']
            package.sendcloud_shipping_tracking_url = parcel['tracking_url']
            if not shipment.shipping_reference:
                shipment.shipping_reference = (
                    parcel.get('colli_tracking_number')
                    or parcel['tracking_number'])
        Package.save(packages)
        shipment.save()

        return 'end'

    def get_parcel(self, shipment, package, credential, usage=None):
        pool = Pool()
        UoM = pool.get('product.uom')
        ModelData = pool.get('ir.model.data')

        cm = UoM(ModelData.get_id('product', 'uom_centimeter'))
        kg = UoM(ModelData.get_id('product', 'uom_kilogram'))
        party = shipment.shipping_to
        address = shipment.shipping_to_address
        phone = address.contact_mechanism_get(
            {'phone', 'mobile'}, usage=usage)
        email = address.contact_mechanism_get('email', usage=usage)
        street_lines = (address.street or '').splitlines()
        name = address_name(address, party)
        company_name = party.full_name if party.full_name != name else None
        parcel = {
            'name': name,
            'company_name': company_name,
            'address': street_lines[0] if street_lines else '',
            'address_2': (
                ' '.join(street_lines[1:]) if len(street_lines) > 1 else ''),
            'house_number': address.numbers,
            'city': address.city,
            'postal_code': address.postal_code,
            'country': address.country.code if address.country else None,
            'country_state': (
                address.subdivision.code.split('-', 1)[1]
                if address.subdivision else None),
            'telephone': phone.value if phone else None,
            'email': email.value if email else None,
            'sender_address': credential.get_sender_address(shipment),
            'external_reference': '/'.join([shipment.number, package.number]),
            'quantity': 1,
            'order_number': shipment.number,
            'request_label': True,
            }
        if address.post_box:
            parcel['to_post_number'] = address.post_box
        if package.total_weight is not None:
            parcel['weight'] = ceil(
                UoM.compute_qty(
                    package.weight_uom, package.total_weight, kg, round=False)
                * 100) / 100
        if (package.length is not None
                and package.width is not None
                and package.height is not None):
            parcel.update(
                length=ceil(
                    UoM.compute_qty(
                        package.length_uom, package.length, cm, round=False)
                    * 100) / 100,
                width=ceil(
                    UoM.compute_qty(
                        package.width_uom, package.width, cm, round=False)
                    * 100) / 100,
                height=ceil(
                    UoM.compute_qty(
                        package.height_uom, package.height, cm, round=False)
                    * 100) / 100)
        shipping_method = credential.get_shipping_method(
            shipment, package=package)
        if shipping_method:
            parcel['shipment'] = {'id': shipping_method}
        else:
            parcel['apply_shipping_rules'] = True
        return parcel


class CreateShippingSendcloud_Customs(metaclass=PoolMeta):
    __name__ = 'stock.shipment.create_shipping.sendcloud'

    def get_parcel(self, shipment, package, credential, usage=None):
        parcel = super().get_parcel(shipment, package, credential, usage=usage)
        if shipment.customs_international:
            parcel['customs_invoice_nr'] = shipment.number
            parcel['customs_shipment_type'] = self.get_customs_shipment_type(
                shipment, credential)
            parcel['export_type'] = self.get_export_type(shipment, credential)
            if description := shipment.shipping_description_used:
                parcel['general_notes'] = description[:500]
            parcel['tax_numbers'] = {
                'sender': list(self.get_tax_numbers(
                        shipment, shipment.company.party, credential)),
                'receiver': list(self.get_tax_numbers(
                        shipment, shipment.shipping_to, credential)),
                }
            if shipment.customs_agent:
                parcel['importer_of_record'] = self.get_importer_of_record(
                    shipment, shipment.customs_agent, credential)
                parcel['tax_numbers']['importer_of_record'] = list(
                    self.get_tax_numbers(
                        shipment, shipment.customs_agent.party, credential))

            parcel_items = []
            for k, v in shipment.customs_products.items():
                parcel_items.append(self.get_parcel_item(shipment, *k, **v))
            parcel['parcel_items'] = parcel_items
        return parcel

    def get_customs_shipment_type(self, shipment, credential):
        return {
            'stock.shipment.out': 2,
            'stock.shipment.in.return': 4,
            }.get(shipment.__class__.__name__)

    def get_export_type(self, shipment, credential):
        if shipment.shipping_to.tax_identifier:
            return 'commercial_b2b'
        else:
            return 'commercial_b2c'

    def get_importer_of_record(
            self, shipment, customs_agent, credential, usage=None):
        address = customs_agent.address
        phone = address.contact_mechanism_get(
            {'phone', 'mobile'}, usage=usage)
        email = address.contact_mechanism_get('email', usage=usage)
        street_lines = (address.street or '').splitlines()
        return {
            'name': customs_agent.party.full_name[:75],
            'address_1': street_lines[0] if street_lines else '',
            'address_2': street_lines[1] if len(street_lines) > 1 else '',
            'house_number': None,  # TODO
            'city': address.city,
            'postal_code': address.postal_code,
            'country_code': address.country.code if address.country else None,
            'country_state': (
                address.subdivision.split('-', 1)[1]
                if address.subdivision else None),
            'telephone': phone.value if phone else None,
            'email': email.value if email else None,
            }

    def get_tax_numbers(self, shipment, party, credential):
        for tax_identifier in party.identifiers:
            if tax_identifier.type == 'br_vat':
                yield {
                    'name': 'CNP',
                    'country_code': 'BR',
                    'value': tax_identifier.code[:100],
                    }
            elif tax_identifier.type == 'ru_vat':
                yield {
                    'name': 'INN',
                    'country_code': 'RU',
                    'value': tax_identifier.code[:100],
                    }
            elif tax_identifier.type == 'eu_vat':
                yield {
                    'name': 'VAT',
                    'country_code': tax_identifier.code[:2],
                    'value': tax_identifier.code[2:][:100],
                    }
            elif tax_identifier.type.endswith('_vat'):
                yield {
                    'name': 'VAT',
                    'country_code': tax_identifier.type[:2].upper(),
                    'value': tax_identifier.code[:100],
                    }
            elif tax_identifier.type in {'us_ein', 'us_ssn'}:
                country, name = tax_identifier.type.upper().split('_')
                yield {
                    'name': name,
                    'country_code': country,
                    'value': tax_identifier.code[:100],
                    }

    def get_parcel_item(
            self, shipment, product, price, currency, unit, quantity, weight):
        tariff_code = product.get_tariff_code({
                'date': shipment.effective_date or shipment.planned_date,
                'country': (
                    shipment.customs_to_country.id
                    if shipment.customs_to_country else None),
                })
        if not quantity.is_integer():
            value = price * Decimal(str(quantity))
            quantity = 1
        else:
            value = price

        return {
            'hs_code': tariff_code.code if tariff_code else None,
            'weight': weight,
            'quantity': quantity,
            'description': product.name[:255],
            'origin_country': (
                product.country_of_origin.code if product.country_of_origin
                else None),
            'value': float(value.quantize(Decimal('.01'))),
            'product_id': product.code,
            }
