import requests
from decimal import getcontext, Decimal, ROUND_HALF_UP
from datetime import datetime
from trytond.model import fields
from trytond.model import ModelSQL, ModelView
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
import logging
from sql import Table

logger = logging.getLogger(__name__)

class Cron(metaclass=PoolMeta):
    __name__ = 'ir.cron'

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.method.selection.append(
            ('automation.cron|update_shipment', "Update Shipment from freight booking info")
        )

class AutomationCron(ModelSQL, ModelView):
    "Automation Cron"
    __name__ = 'automation.cron'

    frequency = fields.Selection([
        ('daily', "Daily"),
        ('weekly', "Weekly"),
        ('monthly', "Monthly"),
    ], "Frequency", required=True,
    help="How frequently rates must be updated.")

    last_update = fields.Date("Last Update", required=True)

    @classmethod
    def run(cls, crons):
        cls.update_shipment()

    @classmethod
    def update_shipment(cls):
        PoolObj = Pool()
        ShipmentIn = PoolObj.get('stock.shipment.in')
        Party = PoolObj.get('party.party')
        Vessel = PoolObj.get('trade.vessel')
        Location = PoolObj.get('stock.location')

        # Table externe
        t = Table('freight_booking_info')
        cursor = Transaction().connection.cursor()
        cursor.execute(*t.select(
            t.ShippingInstructionNumber,
            t.ShippingInstructionDate,
            t.ShippingInstructionQuantity,
            t.ShippingInstructionQuantityUnit,
            t.NumberOfContainers,
            t.ContainerType,
            t.Loading,
            t.Destination,
            t.BookingAgent,
            t.Carrier,
            t.Vessel,
            t.BL_Number,
            t.ETD_Date,
            t.BL_Date,
            t.ExpectedController,
            t.Comments,
            t.FintradeBookingKey,
        ))

        # ---- PREMIÈRE TRANSACTION : Création des objets de référence ----
        with Transaction().new_transaction() as trans1:
            try:
                parties_to_save = []
                vessels_to_save = []
                locations_to_save = []
                
                parties_cache = {}
                vessels_cache = {}
                locations_cache = {}
                
                # Collecter les données des objets de référence
                rows = cursor.fetchall()
                for row in rows:
                    (
                        si_number, si_date, si_quantity, si_unit,
                        container_number, container_type,
                        loading_name, destination_name,
                        agent_name, carrier_name,
                        vessel_name, bl_number,
                        etd_date, bl_date, controller,
                        comments, fintrade_booking_key
                    ) = row

                    # Fonction pour obtenir ou créer un Party
                    def get_or_create_party(name):
                        if not name:
                            return None
                        name_upper = name.upper()
                        if name_upper in parties_cache:
                            return parties_cache[name_upper]
                        
                        # Chercher d'abord dans la base
                        existing = Party.search([('name', '=', name_upper)], limit=1)
                        if existing:
                            parties_cache[name_upper] = existing[0]
                            return existing[0]
                        
                        # Créer un nouveau
                        new_p = Party()
                        new_p.name = name_upper
                        parties_cache[name_upper] = new_p
                        parties_to_save.append(new_p)
                        return new_p

                    # Fonction pour obtenir ou créer un Vessel
                    def get_or_create_vessel(name):
                        if not name:
                            return None
                        name_upper = name.upper()
                        if name_upper in vessels_cache:
                            return vessels_cache[name_upper]
                        
                        existing = Vessel.search([('vessel_name', '=', name_upper)], limit=1)
                        if existing:
                            vessels_cache[name_upper] = existing[0]
                            return existing[0]
                        
                        new_v = Vessel()
                        new_v.vessel_name = name_upper
                        vessels_cache[name_upper] = new_v
                        vessels_to_save.append(new_v)
                        return new_v

                    # Fonction pour obtenir ou créer une Location
                    def get_or_create_location(name, type_):
                        if not name:
                            return None
                        key = f"{name.upper()}_{type_}"
                        if key in locations_cache:
                            return locations_cache[key]
                        
                        existing = Location.search([
                            ('name', '=', name.upper()),
                            ('type', '=', type_)
                        ], limit=1)
                        
                        if existing:
                            locations_cache[key] = existing[0]
                            return existing[0]
                        
                        new_loc = Location()
                        new_loc.name = name.upper()
                        new_loc.type = type_
                        locations_cache[key] = new_loc
                        locations_to_save.append(new_loc)
                        return new_loc

                    # Collecter les objets à créer
                    _ = get_or_create_party(carrier_name)
                    _ = get_or_create_party(agent_name)
                    _ = get_or_create_vessel(vessel_name)
                    _ = get_or_create_location(loading_name, 'supplier')
                    _ = get_or_create_location(destination_name, 'customer')

                # Sauvegarder tous les objets de référence
                if parties_to_save:
                    Party.save(parties_to_save)
                if vessels_to_save:
                    Vessel.save(vessels_to_save)
                if locations_to_save:
                    Location.save(locations_to_save)
                
                trans1.commit()
                logger.info("Première transaction commitée : objets de référence créés")
                
            except Exception as e:
                trans1.rollback()
                logger.error(f"Erreur dans la première transaction : {e}")
                raise

        # ---- DEUXIÈME TRANSACTION : Création des shipments ----
        with Transaction().new_transaction() as trans2:
            try:
                # Recréer les curseurs après la nouvelle transaction
                cursor2 = Transaction().connection.cursor()
                cursor2.execute(*t.select(
                    t.ShippingInstructionNumber,
                    t.ShippingInstructionDate,
                    t.ShippingInstructionQuantity,
                    t.ShippingInstructionQuantityUnit,
                    t.NumberOfContainers,
                    t.ContainerType,
                    t.Loading,
                    t.Destination,
                    t.BookingAgent,
                    t.Carrier,
                    t.Vessel,
                    t.BL_Number,
                    t.ETD_Date,
                    t.BL_Date,
                    t.ExpectedController,
                    t.Comments,
                    t.FintradeBookingKey,
                ))

                rows2 = cursor2.fetchall()
                shipments_to_save = []
                
                for row in rows2:
                    (
                        si_number, si_date, si_quantity, si_unit,
                        container_number, container_type,
                        loading_name, destination_name,
                        agent_name, carrier_name,
                        vessel_name, bl_number,
                        etd_date, bl_date, controller,
                        comments, fintrade_booking_key
                    ) = row

                    # Vérifier si le shipment existe déjà
                    existing_shipment = ShipmentIn.search([
                        ('reference', '=', si_number)
                    ], limit=1)
                    
                    if existing_shipment:
                        logger.info(f"Shipment existe déjà : {si_number}")
                        continue

                    # Récupérer les objets (maintenant ils existent dans la base)
                    carrier = Party.search([('name', '=', carrier_name.upper())], limit=1) if carrier_name else None
                    agent = Party.search([('name', '=', agent_name.upper())], limit=1) if agent_name else None
                    vessel = Vessel.search([('vessel_name', '=', vessel_name.upper())], limit=1) if vessel_name else None
                    loc_from = Location.search([
                        ('name', '=', loading_name.upper()),
                        ('type', '=', 'supplier')
                    ], limit=1) if loading_name else None
                    loc_to = Location.search([
                        ('name', '=', destination_name.upper()),
                        ('type', '=', 'customer')
                    ], limit=1) if destination_name else None

                    # Créer le shipment
                    shipment = ShipmentIn()
                    shipment.reference = si_number
                    shipment.from_location = loc_from[0] if loc_from else None
                    shipment.to_location = loc_to[0] if loc_to else None
                    shipment.carrier = carrier[0] if carrier else None
                    shipment.supplier = agent[0] if agent else None
                    shipment.vessel = vessel[0] if vessel else None
                    shipment.cargo_mode = 'bulk'
                    shipment.bl_number = bl_number
                    shipment.bl_date = bl_date
                    shipment.etd = etd_date
                    
                    shipments_to_save.append(shipment)
                    logger.info(f"Shipment préparé : {si_number}")

                # Sauvegarder tous les shipments
                if shipments_to_save:
                    ShipmentIn.save(shipments_to_save)
                    logger.info(f"{len(shipments_to_save)} shipments créés")
                
                trans2.commit()
                
            except Exception as e:
                trans2.rollback()
                logger.error(f"Erreur dans la deuxième transaction : {e}")
                raise