from trytond.model import ModelSQL, ModelView, Workflow, fields
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.pyson import Bool, Eval, Id, If, PYSONEncoder
from datetime import datetime

class LCOutgoing(ModelSQL, ModelView, Workflow):
    'LC Outgoing'
    __name__ = 'lc.letter.outgoing'

    name = fields.Char('Number')
    type = fields.Selection([
        ('documentary', 'Documentary LC'),
        ('standby', 'Standby LC')
    ], 'Type', required=True)

    # Références aux commandes (achat ET vente)
    purchase = fields.Many2One('purchase.purchase', 'Purchase')
    
    company = fields.Many2One('company.company', 'Company')
    applicant = fields.Many2One('party.party', 'Applicant')
    beneficiary = fields.Many2One('party.party', 'Beneficiary')

    # Banques
    issuing_bank = fields.Many2One('party.party', 'Issuing Bank')
    advising_bank = fields.Many2One('party.party', 'Advising Bank')
    confirming_bank = fields.Many2One('party.party', 'Confirming Bank')
    reimbursing_bank = fields.Many2One('party.party', 'Reimbursing Bank')

    # Montants et conditions
    amount = fields.Numeric('Amount', digits=(16, 2))
    currency = fields.Many2One('currency.currency', 'Currency')
    tolerance_plus = fields.Numeric('Tolerance + %', digits=(6, 2))
    tolerance_minus = fields.Numeric('Tolerance - %', digits=(6, 2))

    # Conditions de livraison
    incoterm = fields.Many2One('incoterm.incoterm', 'Incoterm')
    port_of_loading = fields.Many2One('stock.location','Port of Loading')
    port_of_discharge = fields.Many2One('stock.location','Port of Discharge')
    final_destination = fields.Many2One('stock.location','Final Destination')
    partial_shipment = fields.Selection([
        (None,''),
        ('allowed', 'Allowed'),
        ('not_allowed', 'Not Allowed')
    ], 'Partial Shipment')
    transhipment = fields.Selection([
        (None,''),
        ('allowed', 'Allowed'),
        ('not_allowed', 'Not Allowed')
    ], 'Transhipment')

    # Dates critiques
    latest_shipment_date = fields.Date('Latest Shipment Date')
    issue_date = fields.Date('Issue Date')
    expiry_date = fields.Date('Expiry Date')
    expiry_place = fields.Char('Expiry Place')
    presentation_days = fields.Integer('Presentation Days')

    # Règles et conditions
    ruleset = fields.Selection([
        (None,''),
        ('ucp600', 'UCP 600'),
        ('isp98', 'ISP 98'),
        ('urdg758', 'URDG 758')
    ], 'Ruleset')

    required_documents = fields.Many2Many(
        'contract.document.type', 'lc_out', 'doc_type', 'Required Documents')

    # Workflow principal
    state = fields.Selection([
        ('draft', 'Draft'),
        ('submitted', 'Submitted'),
        ('approved', 'Approved'),
        ('cancelled', 'Cancelled')
    ], 'State', readonly=True)

    version = fields.Integer('Version', readonly=True)

    # Documents et pièces jointes
    attachments = fields.One2Many('ir.attachment', 'resource', 'Attachments',
        domain=[('resource', '=', Eval('id'))], depends=['id'])
    
    # Champs techniques
    swift_message = fields.Text('SWIFT Message')
    swift_type = fields.Char('SWIFT Type')
    bank_reference = fields.Char('Bank Reference')
    our_reference = fields.Char('Our Reference')

    # Champs spécifiques Achat
    bank_instructions = fields.Text('Instructions to Bank')
    application_date = fields.Date('Application Date')
    credit_availability = fields.Selection([
        (None,''),
        ('by_payment', 'By Payment'),
        ('by_deferred_payment', 'By Deferred Payment'),
        ('by_acceptance', 'By Acceptance'),
        ('by_negotiation', 'By Negotiation')
    ], 'Credit Availability')
    
    # Suivi documents fournisseur
    documents_received = fields.One2Many('lc.document.received', 'lc', 'Documents Received')
    documents_status = fields.Function(fields.Selection([
        ('pending', 'Pending'),
        ('partial', 'Partially Received'),
        ('complete', 'Complete'),
        ('discrepant', 'Discrepant')
    ], 'Documents Status'), 'get_documents_status')

    # Dates importantes
    amendment_deadline = fields.Date('Amendment Deadline')
    documents_deadline = fields.Date('Documents Deadline')
    swift_file = fields.Many2One('document.incoming',"Swift file")
    swift_execute = fields.Boolean("Create")
    swift_text = fields.Text("Message")

    @staticmethod
    def default_state():
        return 'draft'

    @staticmethod
    def default_version():
        return 1

    @classmethod
    def __setup__(cls):
        super(LCOutgoing, cls).__setup__()
        cls._transitions = set((
                ('draft', 'submitted'),
                ('submitted', 'approved'),
                ('draft', 'cancelled'),
                ('submitted', 'cancelled'),
                ('cancelled', 'draft'),
                ))
        cls._buttons.update({
                'cancel': {
                    'invisible': Eval('state').in_(['cancelled', 'approved']),
                    'depends': ['state'],
                    },
                'draft': {
                    'invisible': Eval('state') != 'cancelled',
                    'depends': ['state'],
                    },
                'submit': {
                    'invisible': Eval('state') != 'draft',
                    'depends': ['state'],
                    },
                'approve': {
                    'invisible': Eval('state') != 'submitted',
                    'depends': ['state'],
                    },
                })
    
    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, lcs):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, lcs):
        pass
    
    @classmethod
    @ModelView.button
    @Workflow.transition('submitted')
    def submit(cls, lcs):
        cls.write(lcs, {'state': 'submitted'})
        
    @classmethod
    @ModelView.button
    @Workflow.transition('approved')
    def approve(cls, lcs):
        for lc in lcs:
            cls.write([lc], {'state': 'approved'})
    
    def get_rec_name(self, name):
        if self.name:
            return f"{self.name}"
        return f"LC - {self.create_date}"

    @classmethod
    def search_rec_name(cls, name, clause):
        return ['OR',
            ('name',) + tuple(clause[1:]),
        ]

    @classmethod
    def write(cls, lcs, vals):
        # Appeler le parent pour appliquer vals initiaux
        super(LCOutgoing, cls).write(lcs, vals)

        # Puis traiter chaque record si nécessaire.
        # On passe `vals` pour savoir si swift_execute a été fourni dans l'appel initial.
        for lc in lcs:
            cls._process_swift_for_record(lc, initial_vals=vals)

    @classmethod
    def _process_swift_for_record(cls, lc, initial_vals=None):
        """
        Traite un enregistrement unique : récupère swift_text si nécessaire,
        appelle import_swift et applique les writes via super().write
        (pour éviter de ré-appeler notre override write).
        `initial_vals` : dict des valeurs passées lors du create/write initial
        (peut être None).
        """
        # Vérifier si on doit exécuter
        should_execute = False
        if initial_vals and 'swift_execute' in initial_vals:
            should_execute = bool(initial_vals.get('swift_execute'))
        else:
            should_execute = bool(lc.swift_execute)

        if not should_execute:
            return

        update_vals = {}
        swift_message = lc.generate_swift_mt700()
        # Toujours désactiver le flag et passer en submitted
        update_vals['swift_execute'] = False
        update_vals['swift_text'] = swift_message
        # Appliquer les modifications via la méthode parente pour éviter récursion
        if update_vals:
            super(LCOutgoing, cls).write([lc], update_vals)

    @classmethod
    def create_from_purchase(cls, purchase_id):
        """Méthode de base pour création depuis achat"""
        Purchase = Pool().get('purchase.purchase')
        
        purchase = Purchase(purchase_id)
        return {
            'purchase': purchase.id,
            'company': purchase.company.id,
            'applicant': purchase.company.party.id,
            'beneficiary': purchase.party.id,
            'currency': purchase.currency.id if purchase.currency else None,
            'state': 'draft',
            'type': 'documentary',
            'application_date': datetime.now().date(),
            'bank_instructions': 'Standard LC issuance instructions',
        }

    def get_documents_status(self, name):
        if not self.documents_received:
            return 'pending'
        received_count = len([d for d in self.documents_received if d.received])
        required_count = len(self.lc.required_documents) if self.lc.required_documents else 0
        
        if received_count == 0:
            return 'pending'
        elif received_count < required_count:
            return 'partial'
        elif any(d.discrepancy for d in self.documents_received):
            return 'discrepant'
        else:
            return 'complete'

    def generate_swift_mt700(self):
        """Génère le message SWIFT MT700 pour la banque"""
        swift_template = """
:27: SEQUENCE OF TOTAL
1/1

:40A: FORM OF DOCUMENTARY CREDIT
IRREVOCABLE

:20: DOCUMENTARY CREDIT NUMBER
{lc_number}

:31C: DATE OF ISSUE
{issue_date}

:31D: DATE AND PLACE OF EXPIRY
{expiry_date} {expiry_place}

:50: APPLICANT
{applicant}

:59: BENEFICIARY
{beneficiary}

:32B: CURRENCY CODE, AMOUNT
{currency} {amount}

:41A: AVAILABLE WITH... BY...
{available_with}

:43P: PARTIAL SHIPMENTS
{partial_shipment}

:43T: TRANSSHIPMENT
{transhipment}

:44A: LOADING ON BOARD/DISPATCH/TAKING IN CHARGE AT/FROM
{port_of_loading}

:44B: FOR TRANSPORTATION TO
{port_of_discharge}

:44C: LATEST DATE OF SHIPMENT
{latest_shipment_date}

:45A: DESCRIPTION OF GOODS AND/OR SERVICES
{goods_description}

:46A: DOCUMENTS REQUIRED
{documents_required}

:47A: ADDITIONAL CONDITIONS
{additional_conditions}

:71B: CHARGES
ALL BANK CHARGES OUTSIDE ISSUING BANK ARE FOR BENEFICIARY'S ACCOUNT

:48: PERIOD FOR PRESENTATION
{presentation_days} DAYS AFTER SHIPMENT DATE

:49: CONFIRMATION INSTRUCTIONS
WITHOUT

:78: INSTRUCTIONS TO PAYING/ACCEPTING/NEGOTIATING BANK
{bank_instructions}
"""
        lc = self
        swift_text = swift_template.format(
            lc_number=lc.name or "TO_BE_ASSIGNED",
            issue_date=lc.issue_date.strftime("%y%m%d") if lc.issue_date else datetime.now().strftime("%y%m%d"),
            expiry_date=lc.expiry_date.strftime("%y%m%d") if lc.expiry_date else "",
            expiry_place=lc.expiry_place or "",
            applicant=lc.applicant.rec_name if lc.applicant else "",
            beneficiary=lc.beneficiary.rec_name if lc.beneficiary else "",
            currency=lc.currency.code if lc.currency else "",
            amount=str(lc.amount) if lc.amount else "",
            available_with="ANY BANK BY NEGOTIATION",
            partial_shipment=lc.partial_shipment or "NOT ALLOWED",
            transhipment=lc.transhipment or "NOT ALLOWED",
            port_of_loading=lc.port_of_loading.name if lc.port_of_loading else "",
            port_of_discharge=lc.port_of_discharge.name if lc.port_of_discharge else "",
            latest_shipment_date=lc.latest_shipment_date.strftime("%y%m%d") if lc.latest_shipment_date else "",
            goods_description=self._get_goods_description(),
            documents_required=self._get_documents_swift(),
            additional_conditions=self._get_additional_conditions(),
            presentation_days=lc.presentation_days or 21,
            bank_instructions=self.bank_instructions or "PLEASE FORWARD ALL DOCUMENTS TO US IN ONE LOT BY COURIER."
        )
        
        # Crée le message SWIFT
        # SwiftMessage = Pool().get('lc.swift.message')
        # swift_message = SwiftMessage.create([{
        #     'lc': lc.id,
        #     'message_type': 'MT700',
        #     'direction': 'outgoing',
        #     'message_text': swift_text,
        #     'message_date': datetime.now(),
        #     'status': 'draft',
        #     'reference': f"{lc.name}_MT700" if lc.name else None,
        # }])[0]
        
        return swift_text

    def _get_goods_description(self):
        """Description des marchandises pour SWIFT"""
        lc = self
        if lc.purchase and lc.purchase.lines:
            desc = []
            for line in lc.purchase.lines:
                desc.append(f"{line.quantity} {line.unit.rec_name} of {line.product.rec_name}")
            return "\n".join(desc)
        return "AS PER PROFORMA INVOICE"

    def _get_documents_swift(self):
        """Liste des documents pour SWIFT"""
        lc = self
        docs = []
        if lc.required_documents:
            for doc in lc.required_documents:
                docs.append(f"+ {doc.name}")
        return "\n".join(docs) if docs else "COMMERCIAL INVOICE\nPACKING LIST\nBILL OF LADING"

    def _get_additional_conditions(self):
        """Conditions additionnelles"""
        lc = self
        conditions = []
        if lc.incoterm:
            conditions.append(f"INCOTERMS {lc.incoterm.code}")
        if lc.tolerance_plus or lc.tolerance_minus:
            tolerance = f"TOLERANCE {lc.tolerance_plus or 0}/+{lc.tolerance_minus or 0} PERCENT"
            conditions.append(tolerance)
        return "\n".join(conditions)

    def send_to_bank(self):
        """Envoie la LC à la banque"""
        self.lc.state = 'submitted'
        self.lc.save()
        return True