from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool
from trytond.exceptions import UserError
from decimal import Decimal, ROUND_HALF_UP
from datetime import datetime as dt
import datetime
import requests
import logging

logger = logging.getLogger(__name__)

class WeightReport(ModelSQL, ModelView):
    'Weight Report'
    __name__ = 'weight.report'
    _rec_name = 'reference'
    # Identification
    lab = fields.Char('Laboratory', required=True)
    
    # Report Information
    reference = fields.Char('Reference')
    file_no = fields.Char('File Number')
    report_date = fields.Date('Report Date')
    
    # Contract Information
    contract_no = fields.Char('Contract Number')
    invoice_no = fields.Char('Invoice Number')
    lc_no = fields.Char('LC Number')
    origin = fields.Char('Origin')
    commodity = fields.Char('Commodity')
    
    # Parties Information
    seller = fields.Many2One('party.party','Seller', required=True)
    buyer = fields.Many2One('party.party','Buyer', required=True)
    carrier = fields.Many2One('party.party','Carrier')
    
    # Shipment Information
    vessel = fields.Many2One('trade.vessel','Vessel')
    bl_no = fields.Char('B/L Number')
    bl_date = fields.Date('B/L Date')
    port_loading = fields.Many2One('stock.location','Port of Loading')
    port_destination = fields.Many2One('stock.location','Port of Destination')
    arrival_date = fields.Date('Arrival Date')
    weighing_place = fields.Char('Weighing Place')
    weighing_method = fields.Char('Weighing Method')
    weight_date = fields.Date('Weight Date')
    bales = fields.Integer('Number of Bales')
    
    # Weights Information
    gross_landed_kg = fields.Numeric('Gross Landed (kg)', digits=(16, 2))
    tare_kg = fields.Numeric('Tare Weight (kg)', digits=(16, 2))
    net_landed_kg = fields.Numeric('Net Landed (kg)', digits=(16, 2))
    invoice_net_kg = fields.Numeric('Invoice Net (kg)', digits=(16, 2))
    gain_loss_kg = fields.Numeric('Gain/Loss (kg)', digits=(16, 2))
    gain_loss_percent = fields.Numeric('Gain/Loss (%)', digits=(16, 2))
    remote_weight_report_keys = fields.Text('Remote WR Keys', readonly=True)
    remote_weight_report_sent_at = fields.Date(
        'Remote WR Sent At', readonly=True)
    
    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._order = [('report_date', 'DESC')]
        cls._buttons.update({
            'create_remote_weight_reports': {},
        })
    
    def get_rec_name(self, name):
        items = [self.lab]
        if self.reference:
            items.append('[%s]' % self.reference)
        return ' '.join(items)

    def create_remote_weight_report(self, wr_payload):
        response = requests.post(
            "http://automation-service:8006/weight-report",
            json=wr_payload,
            timeout=10
        )
        response.raise_for_status()
        return response.json()

    def get_related_shipments(self):
        ShipmentWR = Pool().get('shipment.wr')
        links = ShipmentWR.search([('wr', '=', self.id)])
        return [link.shipment_in for link in links if link.shipment_in]

    def get_source_shipment(self):
        shipments = self.get_related_shipments()
        if not shipments:
            raise UserError('No shipment is linked to this weight report.')
        unique_shipments = {shipment.id: shipment for shipment in shipments}
        if len(unique_shipments) > 1:
            raise UserError(
                'This weight report is linked to multiple shipments.')
        return next(iter(unique_shipments.values()))

    def get_remote_weight_report_lots(self, shipment):
        lots = []
        seen = set()
        for move in shipment.incoming_moves or []:
            lot = getattr(move, 'lot', None)
            if (not lot or lot.lot_type != 'physic'
                    or lot.id in seen):
                continue
            seen.add(lot.id)
            lots.append(lot)
        if not lots:
            raise UserError(
                'No physical lot was found on the incoming moves.')
        return lots

    def validate_remote_weight_report_context(self, shipment):
        if not shipment.controller:
            raise UserError(
                'A controller is required before creating remote weight reports.')
        if not shipment.returned_id:
            raise UserError(
                'A returned ID is required before creating remote weight reports.')
        if not shipment.agent:
            raise UserError(
                'A booking agent is required before creating remote weight reports.')
        if not shipment.to_location:
            raise UserError(
                'A destination location is required before creating remote weight reports.')
        if not self.bales:
            raise UserError(
                'The global weight report must define the number of bales.')
        if not self.report_date or not self.weight_date:
            raise UserError(
                'Report date and weight date are required.')

    def build_remote_weight_report_payload(self, shipment, lot):
        if not lot.lot_chunk_key:
            raise UserError(
                'Each physical lot must have a chunk key before export.')
        factor_net = self.net_landed_kg / self.bales
        factor_gross = self.gross_landed_kg / self.bales
        lot_ratio = Decimal(lot.lot_qt) / self.bales
        return {
            "chunk_key": lot.lot_chunk_key,
            "gross_weight": float(round(
                Decimal(lot.lot_qt) * factor_gross, 5)),
            "net_weight": float(round(
                Decimal(lot.lot_qt) * factor_net, 5)),
            "tare_total": float(round(self.tare_kg * lot_ratio, 5)),
            "bags": int(lot.lot_qt),
            "surveyor_code": shipment.controller.get_alf(),
            "place_key": shipment.to_location.get_places(),
            "report_date": int(self.report_date.strftime("%Y%m%d")),
            "weight_date": int(self.weight_date.strftime("%Y%m%d")),
            "agent": shipment.agent.get_alf(),
            "forwarder_ref": shipment.returned_id,
        }

    @classmethod
    @ModelView.button
    def create_remote_weight_reports(cls, reports):
        to_save = []
        for report in reports:
            shipment = report.get_source_shipment()
            report.validate_remote_weight_report_context(shipment)
            lots = report.get_remote_weight_report_lots(shipment)
            created = []
            for lot in lots:
                payload = report.build_remote_weight_report_payload(
                    shipment, lot)
                logger.info("REMOTE_WR_PAYLOAD:%s", payload)
                data = report.create_remote_weight_report(payload)
                created.append(
                    f"{lot.rec_name}: {data.get('weight_report_key')}")
            report.remote_weight_report_keys = '\n'.join(created)
            report.remote_weight_report_sent_at = datetime.date.today()
            to_save.append(report)
        if to_save:
            cls.save(to_save)
    # @classmethod
    # @ModelView.button_action('weight_report.act_import_json')
    # def import_json(cls, reports):
    #     pass
    
    # @classmethod
    # @ModelView.button_action('weight_report.act_export_json')
    # def export_json(cls, reports):
    #     pass

    @classmethod
    def create_from_json(cls, json_data):
        """Crée un rapport à partir de données JSON"""
        pool = Pool()
        Party = pool.get('party.party')
        Vessel = pool.get('trade.vessel')
        Location = pool.get('stock.location')
        
        # Préparer les données
        report = {}
        
        # 1. Identification (champs simples)
        report['lab'] = json_data.get('lab', '')
        
        # 2. Report Information
        report_data = json_data.get('report', {})
        report['reference'] = report_data.get('reference', '')
        report['file_no'] = report_data.get('file_no', '')
        
        # Conversion de la date (format: "28 October 2025")
        
        def parse_date(date_str):
            logger.info("TRY_TO_PARSE:%s",date_str)
            if not date_str:
                return None
            if isinstance(date_str, datetime.date):
                return date_str
            if not isinstance(date_str, str):
                date_str = str(date_str)
            for fmt in ('%d %B %Y', '%d %b %Y'):
                try:
                    return dt.strptime(date_str, fmt).date()
                except ValueError:
                    pass
            return None

        def parse_bales(value):
            if value in (None, ''):
                return 0
            if isinstance(value, (int, float, Decimal)):
                return int(Decimal(str(value)).quantize(Decimal('1')))
            text = str(value).strip()
            if not text:
                return 0
            text = text.replace(' ', '')
            if ',' in text and '.' in text:
                text = text.replace(',', '')
            elif ',' in text:
                text = text.replace(',', '.')
            try:
                decimal_value = Decimal(text)
            except Exception:
                if '.' in text:
                    decimal_value = Decimal(text.replace('.', ''))
                else:
                    raise
            return int(decimal_value.quantize(Decimal('1')))

        report['report_date'] = parse_date(report_data.get('date', ''))
        # 3. Contract Information
        contract_data = json_data.get('contract', {})
        report['contract_no'] = contract_data.get('contract_no', '')
        report['invoice_no'] = contract_data.get('invoice_no', '')
        report['lc_no'] = contract_data.get('lc_no', '')
        report['origin'] = contract_data.get('origin', '')
        report['commodity'] = contract_data.get('commodity', '')
        
        # 4. Parties Information (Many2One)
        parties_data = json_data.get('parties', {})
        
        # Recherche ou création des parties
        seller_name = parties_data.get('seller', '')
        if seller_name:
            seller = Party.getPartyByName(seller_name)
            report['seller'] = seller.id if seller else None
        
        buyer_name = parties_data.get('buyer', '')
        if buyer_name:
            buyer = Party.getPartyByName(buyer_name)
            report['buyer'] = buyer.id if buyer else None
        
        carrier_name = parties_data.get('carrier', '')
        if carrier_name:
            carrier = Party.getPartyByName(carrier_name)
            report['carrier'] = carrier.id if carrier else None
        
        # 5. Shipment Information
        shipment_data = json_data.get('shipment', {})
        
        # Recherche du navire par nom
        vessel_name = shipment_data.get('vessel', '')
        if vessel_name:
            vessel = Vessel.search([('vessel_name', '=', vessel_name)], limit=1)
            report['vessel'] = vessel[0].id if vessel else None
        
        report['bl_no'] = shipment_data.get('bl_no', '')
        
        # Conversion de la date B/L (format: "16-Aug-2025")
        bl_date_str = shipment_data.get('bl_date', '')
        if bl_date_str:
            try:
                report['bl_date'] = dt.strptime(bl_date_str, '%d-%b-%Y').date()
            except:
                report['bl_date'] = None
        
        # Ports (Many2One - nécessite recherche par nom)
        port_loading_name = shipment_data.get('port_loading', '')
        if port_loading_name:
            port_loading = Location.search([
                ('name', '=', port_loading_name),
                ('type', '=', 'supplier')
            ], limit=1)
            report['port_loading'] = port_loading[0].id if port_loading else None
        
        port_destination_name = shipment_data.get('port_destination', '')
        if port_destination_name:
            port_destination = Location.search([
                ('name', '=', port_destination_name),
                ('type', '=', 'customer')
            ], limit=1)
            report['port_destination'] = port_destination[0].id if port_destination else None
        
        # Conversion de la date d'arrivée (format: "20-Oct-2025")
        arrival_date_str = shipment_data.get('arrival_date', '')
        if arrival_date_str:
            try:
                report['arrival_date'] = dt.strptime(arrival_date_str, '%d-%b-%Y').date()
            except:
                report['arrival_date'] = None
        
        report['weighing_place'] = shipment_data.get('weighing_place', '')
        report['weighing_method'] = shipment_data.get('weighing_method', '')
        report['bales'] = parse_bales(shipment_data.get('bales', 0))
        
        # 6. Weights Information
        weights_data = json_data.get('weights', {})
    
        gross = Decimal(str(weights_data.get('gross_landed_kg', 0) or 0))
        tare = Decimal(str(weights_data.get('tare_kg', 0) or 0))
        net = Decimal(str(weights_data.get('net_landed_kg', 0) or 0))
        invoice = Decimal(str(weights_data.get('invoice_net_kg', 0) or 0))
        gain_loss = Decimal(str(weights_data.get('gain_loss_kg', 0) or 0))
        gain_loss_percent = Decimal(str(weights_data.get('gain_loss_percent', 0) or 0))
        
        # Arrondir à 2 décimales
        report['weight_date'] = parse_date(weights_data.get('weight_date', ''))
        report['gross_landed_kg'] = gross.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        report['tare_kg'] = tare.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        report['net_landed_kg'] = net.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        report['invoice_net_kg'] = invoice.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        report['gain_loss_kg'] = gain_loss.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
        report['gain_loss_percent'] = gain_loss_percent.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

        # 7. Création du rapport
        return cls.create([report])[0]
