# -*- coding: utf-8 -*-
# Copyright 2024-2025 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Claim Assistant product family.

import base64
from datetime import datetime, date
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import logging

_logger = logging.getLogger(__name__)


class FusionCentralExportWizard(models.TransientModel):
    _name = 'fusion_claims.export.wizard'
    _description = 'Fusion Central ADP Export Wizard'

    invoice_ids = fields.Many2many(
        'account.move',
        string='Invoices',
        domain=[('move_type', 'in', ['out_invoice', 'out_refund'])],
    )
    vendor_code = fields.Char(
        string='Vendor Code',
        required=True,
    )
    export_date = fields.Date(
        string='Export Date',
        default=fields.Date.today,
        required=True,
    )
    
    # Export result
    export_file = fields.Binary(string='Export File', readonly=True)
    export_filename = fields.Char(string='Filename', readonly=True)
    state = fields.Selection([
        ('draft', 'Draft'),
        ('done', 'Done'),
    ], default='draft')
    export_summary = fields.Text(string='Export Summary', readonly=True)
    saved_to_export_records = fields.Boolean(string='Saved to Export Files', readonly=True)
    warnings = fields.Text(string='Warnings', readonly=True)

    @api.model
    def default_get(self, fields_list):
        res = super().default_get(fields_list)
        
        # Get vendor code from settings
        ICP = self.env['ir.config_parameter'].sudo()
        res['vendor_code'] = ICP.get_param('fusion_claims.vendor_code', '')
        
        # Get invoices from context
        if self._context.get('active_ids'):
            invoices = self.env['account.move'].browse(self._context['active_ids'])
            # Filter to only customer invoices/refunds
            invoices = invoices.filtered(lambda m: m.move_type in ['out_invoice', 'out_refund'])
            res['invoice_ids'] = [(6, 0, invoices.ids)]
        
        return res

    def _get_field_value(self, record, field_name, default=''):
        """Get field value safely."""
        return getattr(record, field_name, default) or default

    def _format_date(self, date_val):
        """Format date as ddmmyyyy."""
        if not date_val:
            return ''
        if isinstance(date_val, str):
            try:
                date_val = datetime.strptime(date_val, '%Y-%m-%d').date()
            except ValueError:
                return ''
        return date_val.strftime('%d%m%Y')

    def _should_skip_line(self, line):
        """Check if line should be skipped based on device code."""
        code = line._get_adp_device_code().upper() if hasattr(line, '_get_adp_device_code') else ''
        skip_codes = ['FUNDING', 'NON-FUNDED', 'N/A', 'NA', 'NON-ADP', 'LABOUR', 'DELIVERY', '']
        return code in skip_codes or not code

    def _validate_dates(self, invoice):
        """Validate dates and return warnings."""
        warnings = []
        today = date.today()
        
        invoice_date = invoice.invoice_date
        delivery_date = invoice.x_fc_adp_delivery_date
        
        # Check for future dates
        if invoice_date and invoice_date > today:
            warnings.append(f"Invoice {invoice.name}: Invoice date is in the future")
        
        if delivery_date and delivery_date > today:
            warnings.append(f"Invoice {invoice.name}: Delivery date is in the future")
        
        # Check delivery date vs invoice date
        if delivery_date and invoice_date and delivery_date > invoice_date:
            warnings.append(f"Invoice {invoice.name}: Delivery date is after invoice date")
        
        return warnings

    def _verify_portions(self, adp_device_price, client_type, deduction_type, deduction_value,
                         stored_adp_portion, stored_client_portion, quantity=1, tolerance=0.01):
        """Verify stored portions against calculated portions.
        
        Recalculates portions using the ADP database price and compares with stored values.
        This ensures the invoice calculations match the ADP database.
        
        IMPORTANT: Invoice lines store TOTAL portions (qty × unit portion).
        This method calculates unit portions and multiplies by quantity for comparison.
        
        Args:
            adp_device_price: ADP approved price from database (per unit)
            client_type: Client type (REG, ODS, OWP, ACS, LTC, SEN, CCA)
            deduction_type: 'pct', 'amt', 'none', or None
            deduction_value: Deduction value (per unit for AMT type)
            stored_adp_portion: Pre-calculated TOTAL ADP portion from invoice line
            stored_client_portion: Pre-calculated TOTAL client portion from invoice line
            quantity: Line quantity (used to calculate expected totals)
            tolerance: Acceptable difference for floating point comparison (default $0.01)
        
        Returns:
            tuple: (is_valid, expected_adp_total, expected_client_total, error_message)
        """
        # Determine base percentage by client type
        if client_type == 'REG':
            base_adp_pct = 0.75
        else:
            # ODS, OWP, ACS, LTC, SEN, CCA: 100% ADP, 0% Client
            base_adp_pct = 1.0
        
        # Calculate expected PER-UNIT portions (same logic as invoice)
        if deduction_type == 'pct' and deduction_value:
            # PCT: ADP covers (deduction_value)% of their normal portion
            effective_pct = base_adp_pct * (deduction_value / 100)
            unit_adp = adp_device_price * effective_pct
        elif deduction_type == 'amt' and deduction_value:
            # AMT: Subtract fixed amount from base ADP portion (per unit)
            base_adp = adp_device_price * base_adp_pct
            unit_adp = max(0, base_adp - deduction_value)
        else:
            # No deduction
            unit_adp = adp_device_price * base_adp_pct
        
        unit_client = adp_device_price - unit_adp
        
        # Calculate expected TOTALS (unit × quantity)
        expected_adp_total = unit_adp * quantity
        expected_client_total = unit_client * quantity
        
        # Compare with stored values (allow small tolerance for rounding)
        # Tolerance scales with quantity to account for accumulated rounding
        scaled_tolerance = tolerance * max(1, quantity)
        adp_diff = abs(stored_adp_portion - expected_adp_total)
        client_diff = abs(stored_client_portion - expected_client_total)
        
        if adp_diff > scaled_tolerance or client_diff > scaled_tolerance:
            error_msg = (
                f"Calculation mismatch!\n"
                f"  ADP Device Price: ${adp_device_price:.2f} × {quantity} = ${adp_device_price * quantity:.2f}\n"
                f"  Client Type: {client_type} ({int(base_adp_pct*100)}% ADP)\n"
                f"  Deduction: {deduction_type or 'None'}"
                f"{f' = {deduction_value}' if deduction_value else ''}\n"
                f"  Per Unit: ADP=${unit_adp:.2f}, Client=${unit_client:.2f}\n"
                f"  Expected Total (×{quantity}): ADP=${expected_adp_total:.2f}, Client=${expected_client_total:.2f}\n"
                f"  Invoice has: ADP=${stored_adp_portion:.2f}, Client=${stored_client_portion:.2f}\n"
                f"  Difference: ADP=${adp_diff:.2f}, Client=${client_diff:.2f}"
            )
            return False, expected_adp_total, expected_client_total, error_msg
        
        return True, expected_adp_total, expected_client_total, None

    def _generate_claim_lines(self, invoice):
        """Generate claim lines for an invoice.
        
        Uses PRE-CALCULATED values from invoice lines (x_fc_adp_portion, x_fc_client_portion)
        but VERIFIES them against the ADP database before export.
        
        This ensures:
        1. Single source of truth - values from invoice
        2. Verification - calculations match ADP database
        3. Deductions included in verification
        """
        lines = []
        verification_errors = []
        ADPDevice = self.env['fusion.adp.device.code'].sudo()
        
        client_type = invoice._get_client_type() or 'REG'
        invoice_date = invoice.invoice_date or invoice.date
        delivery_date = invoice.x_fc_adp_delivery_date
        claim_number = invoice.x_fc_claim_number or ''
        client_ref_2 = invoice.x_fc_client_ref_2 or ''
        
        for line in invoice.invoice_line_ids:
            # Skip non-product lines
            if not line.product_id or line.display_type in ('line_section', 'line_note'):
                continue
            
            # Skip lines with excluded device codes
            if self._should_skip_line(line):
                continue
            
            # Get device code
            device_code = line._get_adp_device_code()
            if not device_code:
                continue
            
            # Get ADP approved device price from database for verification
            adp_device = ADPDevice.search([
                ('device_code', '=', device_code),
                ('active', '=', True)
            ], limit=1)
            
            if not adp_device:
                _logger.warning(f"ADP device code {device_code} not found in database, skipping line")
                continue
            
            adp_device_price = adp_device.adp_price or 0
            if not adp_device_price:
                _logger.warning(f"ADP device {device_code} has no price, skipping line")
                continue
            
            # Get quantity first (needed for verification)
            qty = int(line.quantity) if line.quantity else 1
            
            # Get PRE-CALCULATED TOTAL portions from invoice line
            # These are TOTALS (unit × qty), calculated when the invoice was created
            stored_adp_portion = line.x_fc_adp_portion or 0
            stored_client_portion = line.x_fc_client_portion or 0
            
            # Get deduction info for verification
            deduction_type = line.x_fc_deduction_type or 'none'
            deduction_value = line.x_fc_deduction_value or 0
            
            # VERIFY: Recalculate and compare with stored values
            # Pass quantity so verification compares totals correctly
            is_valid, expected_adp, expected_client, error_msg = self._verify_portions(
                adp_device_price=adp_device_price,
                client_type=client_type,
                deduction_type=deduction_type if deduction_type != 'none' else None,
                deduction_value=deduction_value,
                stored_adp_portion=stored_adp_portion,
                stored_client_portion=stored_client_portion,
                quantity=qty,
            )
            
            if not is_valid:
                verification_errors.append(
                    f"Invoice {invoice.name}, Line: {line.product_id.name} ({device_code})\n{error_msg}"
                )
                continue  # Skip lines with mismatched calculations
            
            serial_number = line._get_serial_number()
            
            # Calculate PER-UNIT portions for export (each export line is qty=1)
            unit_adp_portion = stored_adp_portion / qty if qty > 0 else stored_adp_portion
            unit_client_portion = stored_client_portion / qty if qty > 0 else stored_client_portion
            
            # Export one line per unit (ADP expects qty=1 per line)
            for i in range(qty):
                lines.append({
                    'vendor_code': self.vendor_code,
                    'claim_number': claim_number,
                    'client_ref_2': client_ref_2,
                    'invoice_number': invoice.name,
                    'invoice_date': self._format_date(invoice_date),
                    'delivery_date': self._format_date(delivery_date),
                    'device_code': device_code,
                    'serial_number': serial_number,
                    'qty': '1',
                    'device_price': f"{adp_device_price:.2f}",
                    'adp_portion': f"{unit_adp_portion:.2f}",
                    'client_portion': f"{unit_client_portion:.2f}",
                    'client_type': client_type,
                })
        
        # If there were verification errors, raise them
        if verification_errors:
            raise UserError(_(
                "ADP Export Verification Failed!\n\n"
                "The following invoice lines have calculation mismatches between "
                "the invoice and the ADP device database:\n\n%s\n\n"
                "Please verify the invoice calculations and ADP device prices."
            ) % '\n\n'.join(verification_errors))
        
        return lines

    def _generate_export_content(self):
        """Generate the full export content."""
        all_lines = []
        all_warnings = []
        
        for invoice in self.invoice_ids:
            if not invoice._is_adp_invoice():
                continue
            
            # Validate dates
            all_warnings.extend(self._validate_dates(invoice))
            
            # Generate lines
            all_lines.extend(self._generate_claim_lines(invoice))
        
        if not all_lines:
            raise UserError(_("No valid lines to export. Make sure invoices are ADP type and have valid products."))
        
        # Build CSV content (no header)
        # ADP Format: vendor_code,claim_number,client_ref_2,invoice_number,invoice_date,delivery_date,
        #             ,,device_code,serial_number,,qty,device_price,adp_portion,client_portion,client_type
        # Note: device_price is the ADP approved price from fusion.adp.device.code database
        content_lines = []
        for line in all_lines:
            row = ','.join([
                line['vendor_code'],
                line['claim_number'],
                line['client_ref_2'],
                line['invoice_number'],
                line['invoice_date'],
                line['delivery_date'],
                '',  # Reserved field 1 (empty)
                '',  # Reserved field 2 (empty)
                line['device_code'],
                line['serial_number'],
                '',  # Reserved field 3 (empty)
                line['qty'],
                line['device_price'],
                line['adp_portion'],
                line['client_portion'],
                line['client_type'],
            ])
            content_lines.append(row)
        
        return '\n'.join(content_lines), len(all_lines), all_warnings

    def _get_export_filename(self):
        """Generate filename for ADP export.
        
        ADP requires a specific filename format: VENDORCODE_YYYY-MM-DD.txt
        We do NOT add submission numbers because ADP won't accept renamed files.
        User must manually rename if submitting multiple times on same day.
        """
        return f"{self.vendor_code}_{self.export_date.strftime('%Y-%m-%d')}.txt"
    
    def _check_existing_file(self, filename):
        """Check if a file with the same name already exists in ADP Export Records.

        Returns:
            tuple: (exists: bool, existing_files: list of description strings)
        """
        existing_files = []
        ExportRecord = self.env['fusion_claims.adp.export.record']
        existing = ExportRecord.search([('name', '=', filename)])
        if existing:
            existing_files = [
                f"{rec.name} (exported: {rec.export_date.strftime('%Y-%m-%d %H:%M')})"
                for rec in existing
            ]
            return True, existing_files
        return False, existing_files

    def action_export(self):
        """Perform the export.

        Flow:
        1. Validate inputs
        2. Generate content (includes verification - may raise UserError)
        3. Check for existing files (warn but don't block)
        4. Save to ADP Export Records (filestore-backed)
        5. Show download window
        """
        self.ensure_one()

        if not self.invoice_ids:
            raise UserError(_("Please select at least one invoice to export."))

        if not self.vendor_code:
            raise UserError(_("Please enter a vendor code."))

        filename = self._get_export_filename()

        file_exists, existing_files = self._check_existing_file(filename)

        content, line_count, warnings = self._generate_export_content()

        file_data = base64.b64encode(content.encode('utf-8'))

        if file_exists:
            warnings.append(
                f"WARNING: A file with the name '{filename}' already exists in Export Files.\n"
                f"Existing files: {', '.join(existing_files)}\n"
                f"ADP does not accept renamed files. You will need to manually rename "
                f"before submitting if this is a resubmission."
            )

        warnings_text = '\n'.join(warnings) if warnings else ''

        saved = self._save_to_export_records(filename, file_data, line_count)

        for invoice in self.invoice_ids:
            invoice.write({
                'adp_exported': True,
                'adp_export_date': fields.Datetime.now(),
                'adp_export_count': invoice.adp_export_count + 1,
            })

        summary = _("Exported %d lines from %d invoices.") % (line_count, len(self.invoice_ids))
        if saved:
            summary += "\n" + _("File saved to Fusion Claims > ADP > Export Files.")

        self.write({
            'export_file': file_data,
            'export_filename': filename,
            'state': 'done',
            'export_summary': summary,
            'saved_to_export_records': saved,
            'warnings': warnings_text,
        })

        return {
            'type': 'ir.actions.act_window',
            'res_model': self._name,
            'res_id': self.id,
            'view_mode': 'form',
            'target': 'new',
        }

    def _save_to_export_records(self, filename, file_data, line_count):
        """Save export file to the ADP Export Records model (filestore-backed)."""
        try:
            ExportRecord = self.env['fusion_claims.adp.export.record']
            posting_date = ExportRecord._get_current_posting_date(self.export_date)

            ExportRecord.create({
                'name': filename,
                'filename': filename,
                'file_data': file_data,
                'export_date': fields.Datetime.now(),
                'posting_period_date': posting_date,
                'vendor_code': self.vendor_code,
                'line_count': line_count,
                'invoice_ids': [(6, 0, self.invoice_ids.ids)],
                'user_id': self.env.uid,
                'company_id': self.env.company.id,
            })

            _logger.info("Saved ADP export record: %s", filename)
            return True

        except Exception as e:
            _logger.error("Could not save ADP export record: %s", str(e))
            return False
