# -*- 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.

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

_logger = logging.getLogger(__name__)


class DeviceApprovalWizard(models.TransientModel):
    """Wizard to confirm which device types were approved by ADP and set deductions.
    
    This is Stage 2 of the two-stage verification system:
    - Stage 1 (Submission): What device types were submitted (stored in x_fc_submitted_device_types)
    - Stage 2 (Approval): What device types were approved by ADP (this wizard)
    """
    _name = 'fusion_claims.device.approval.wizard'
    _description = 'ADP Device Approval Wizard'

    # ==========================================================================
    # MAIN FIELDS
    # ==========================================================================
    sale_order_id = fields.Many2one(
        'sale.order',
        string='Sale Order',
        required=True,
        readonly=True,
    )
    line_ids = fields.One2many(
        'fusion_claims.device.approval.wizard.line',
        'wizard_id',
        string='Device Lines',
    )
    all_approved = fields.Boolean(
        string='All Approved',
        default=True,
        help='Check this to approve all devices at once',
    )
    has_unapproved = fields.Boolean(
        string='Has Unapproved Devices',
        compute='_compute_has_unapproved',
    )
    has_deductions = fields.Boolean(
        string='Has Deductions',
        compute='_compute_has_deductions',
        help='True if any device has a deduction applied',
    )
    has_invoices = fields.Boolean(
        string='Has Invoices',
        compute='_compute_has_invoices',
        help='True if invoices already exist for this order',
    )
    
    # Stage 1 comparison fields
    submitted_device_types = fields.Text(
        string='Submitted Device Types',
        compute='_compute_submitted_device_types',
        help='Device types that were verified during submission (Stage 1)',
    )
    has_submission_data = fields.Boolean(
        string='Has Submission Data',
        compute='_compute_submitted_device_types',
    )
    
    # Claim Number - required for Mark as Approved
    claim_number = fields.Char(
        string='Claim Number',
        help='ADP Claim Number from the approval letter',
    )
    
    approval_date = fields.Date(
        string='Approval Date',
        default=fields.Date.context_today,
        help='Date ADP approved the application. Defaults to today but can be changed '
             'if ADP approved before the letter was received.',
    )
    
    # Approval Documents - for Mark as Approved mode
    is_mark_approved_mode = fields.Boolean(
        string='Mark Approved Mode',
        compute='_compute_is_mark_approved_mode',
    )
    approval_letter = fields.Binary(
        string='ADP Approval Letter',
        help='Upload the ADP approval letter PDF',
    )
    approval_letter_filename = fields.Char(
        string='Approval Letter Filename',
    )
    # For multiple approval photos, we'll use a Many2many to ir.attachment
    approval_photo_ids = fields.Many2many(
        'ir.attachment',
        'device_approval_wizard_attachment_rel',
        'wizard_id',
        'attachment_id',
        string='Approval Screenshots',
        help='Upload screenshots from the ADP approval document',
    )
    
    @api.depends_context('mark_as_approved')
    def _compute_is_mark_approved_mode(self):
        for wizard in self:
            wizard.is_mark_approved_mode = self.env.context.get('mark_as_approved', False)

    # ==========================================================================
    # COMPUTED FIELDS
    # ==========================================================================
    @api.depends('line_ids.approved')
    def _compute_has_unapproved(self):
        for wizard in self:
            wizard.has_unapproved = any(not line.approved for line in wizard.line_ids)

    @api.depends('line_ids.deduction_type')
    def _compute_has_deductions(self):
        for wizard in self:
            wizard.has_deductions = any(
                line.deduction_type and line.deduction_type != 'none' 
                for line in wizard.line_ids
            )

    @api.depends('sale_order_id', 'sale_order_id.invoice_ids')
    def _compute_has_invoices(self):
        for wizard in self:
            if wizard.sale_order_id:
                wizard.has_invoices = bool(wizard.sale_order_id.invoice_ids.filtered(
                    lambda inv: inv.state != 'cancel'
                ))
            else:
                wizard.has_invoices = False

    @api.depends('sale_order_id', 'sale_order_id.x_fc_submitted_device_types')
    def _compute_submitted_device_types(self):
        """Compute the submitted device types from Stage 1 for comparison display."""
        import json
        for wizard in self:
            if wizard.sale_order_id and wizard.sale_order_id.x_fc_submitted_device_types:
                try:
                    data = json.loads(wizard.sale_order_id.x_fc_submitted_device_types)
                    # Format as readable list
                    submitted = [dt for dt, selected in data.items() if selected]
                    wizard.submitted_device_types = '\n'.join([f'• {dt}' for dt in sorted(submitted)])
                    wizard.has_submission_data = bool(submitted)
                except (json.JSONDecodeError, TypeError):
                    wizard.submitted_device_types = ''
                    wizard.has_submission_data = False
            else:
                wizard.submitted_device_types = ''
                wizard.has_submission_data = False

    @api.onchange('all_approved')
    def _onchange_all_approved(self):
        """Toggle all lines when 'All Approved' is changed.
        
        Only triggers when toggling ON - sets all devices to approved.
        When toggling OFF, users manually uncheck individual items.
        """
        if self.all_approved:
            # Iterate and set approved flag - avoid replacing the entire line
            for line in self.line_ids:
                if not line.approved:
                    line.approved = True

    # ==========================================================================
    # DEFAULT GET - Populate with order lines
    # ==========================================================================
    @api.model
    def default_get(self, fields_list):
        res = super().default_get(fields_list)
        
        active_id = self._context.get('active_id')
        if not active_id:
            return res
        
        order = self.env['sale.order'].browse(active_id)
        res['sale_order_id'] = order.id
        
        # Build line data from order lines that have ADP device codes ONLY
        # Non-ADP items are excluded from the verification list
        ADPDevice = self.env['fusion.adp.device.code'].sudo()
        lines_data = []
        
        for so_line in order.order_line:
            # Skip non-product lines
            if so_line.display_type in ('line_section', 'line_note'):
                continue
            if not so_line.product_id or so_line.product_uom_qty <= 0:
                continue
            
            # Get device code and look up device type
            device_code = so_line._get_adp_device_code()
            
            # SKIP items without a valid ADP device code in the database
            # These are non-ADP items and don't need verification
            if not device_code:
                continue
            
            adp_device = ADPDevice.search([('device_code', '=', device_code), ('active', '=', True)], limit=1)
            
            # Skip if device code not found in ADP database (non-ADP item)
            if not adp_device:
                continue
            
            device_type = adp_device.device_type or ''
            device_description = adp_device.device_description or ''
            
            # Default to NOT approved - user must actively check each item
            # Unless it was already approved in a previous verification
            is_approved = so_line.x_fc_adp_approved if so_line.x_fc_adp_approved else False
            
            lines_data.append((0, 0, {
                'sale_line_id': so_line.id,
                'product_name': so_line.product_id.display_name,
                'device_code': device_code,
                'device_type': device_type or so_line.x_fc_adp_device_type or '',
                'device_description': device_description,
                'serial_number': so_line.x_fc_serial_number or '',
                'quantity': so_line.product_uom_qty,
                'unit_price': so_line.price_unit,
                'adp_portion': so_line.x_fc_adp_portion,
                'client_portion': so_line.x_fc_client_portion,
                'approved': is_approved,
                'deduction_type': so_line.x_fc_deduction_type or 'none',
                'deduction_value': so_line.x_fc_deduction_value or 0,
            }))
        
        res['line_ids'] = lines_data
        
        # All Approved checkbox - only true if ALL lines are already approved
        if lines_data:
            all_approved = all(line[2].get('approved', False) for line in lines_data)
            res['all_approved'] = all_approved
        else:
            res['all_approved'] = True  # No ADP items = nothing to verify
        
        return res

    # ==========================================================================
    # ACTION METHODS
    # ==========================================================================
    def action_confirm_approval(self):
        """Confirm the approval status and deductions, update order lines and invoices."""
        self.ensure_one()
        
        approved_count = 0
        unapproved_count = 0
        deduction_count = 0
        updated_lines = self.env['sale.order.line']
        
        for wiz_line in self.line_ids:
            if wiz_line.sale_line_id:
                # NOTE: Do NOT write x_fc_adp_device_type here - it's a computed field
                # that should be computed from the product's device code
                vals = {
                    'x_fc_adp_approved': wiz_line.approved,
                    'x_fc_deduction_type': wiz_line.deduction_type or 'none',
                    'x_fc_deduction_value': wiz_line.deduction_value or 0,
                }
                wiz_line.sale_line_id.write(vals)
                updated_lines |= wiz_line.sale_line_id
                
                if wiz_line.approved:
                    approved_count += 1
                else:
                    unapproved_count += 1
                
                if wiz_line.deduction_type and wiz_line.deduction_type != 'none':
                    deduction_count += 1
        
        # MARK VERIFICATION AS COMPLETE on the sale order
        # This allows invoice creation even if some items are unapproved
        if self.sale_order_id:
            self.sale_order_id.write({'x_fc_device_verification_complete': True})
            
            # FORCE RECALCULATION of ADP/Client portions on all order lines
            # This ensures unapproved items get 100% assigned to client portion
            for line in self.sale_order_id.order_line:
                line._compute_adp_portions()
            # Recalculate order totals
            self.sale_order_id._compute_adp_totals()
        
        # If we're in "mark_as_approved" mode, also update the status and save approval documents
        if self.env.context.get('mark_as_approved') and self.sale_order_id:
            # Determine status based on whether there are deductions
            new_status = 'approved_deduction' if deduction_count > 0 else 'approved'
            
            update_vals = {
                'x_fc_adp_application_status': new_status,
                'x_fc_claim_approval_date': self.approval_date or fields.Date.context_today(self),
            }
            
            # Save claim number if provided
            if self.claim_number:
                update_vals['x_fc_claim_number'] = self.claim_number
            
            # Save approval letter if uploaded
            if self.approval_letter:
                update_vals['x_fc_approval_letter'] = self.approval_letter
                update_vals['x_fc_approval_letter_filename'] = self.approval_letter_filename

            self.sale_order_id.with_context(skip_status_validation=True).write(update_vals)
            
            # Collect attachment IDs for chatter post
            chatter_attachment_ids = []
            
            # IMPORTANT: When files are uploaded via many2many_binary to a transient model,
            # they are linked to the wizard and may be garbage collected when the wizard closes.
            # We need to COPY the attachment data to create persistent attachments linked to the sale order.
            
            photos_attached = 0
            if self.approval_photo_ids:
                photo_ids_to_link = []
                IrAttachment = self.env['ir.attachment'].sudo()
                
                for attachment in self.approval_photo_ids:
                    # Create a NEW attachment linked to the sale order (copy the data)
                    # This ensures the attachment persists after the wizard is deleted
                    new_attachment = IrAttachment.create({
                        'name': attachment.name or f'approval_screenshot_{photos_attached + 1}',
                        'datas': attachment.datas,
                        'mimetype': attachment.mimetype,
                        'res_model': 'sale.order',
                        'res_id': self.sale_order_id.id,
                        'type': 'binary',
                    })
                    chatter_attachment_ids.append(new_attachment.id)
                    photo_ids_to_link.append(new_attachment.id)
                    photos_attached += 1
                
                # Link photos to the Many2many field for easy access in ADP Documents tab
                if photo_ids_to_link:
                    existing_photo_ids = self.sale_order_id.x_fc_approval_photo_ids.ids
                    self.sale_order_id.write({
                        'x_fc_approval_photo_ids': [(6, 0, existing_photo_ids + photo_ids_to_link)]
                    })

            # Create attachment for approval letter if uploaded
            if self.approval_letter:
                letter_attachment = self.env['ir.attachment'].create({
                    'name': self.approval_letter_filename or 'ADP_Approval_Letter.pdf',
                    'datas': self.approval_letter,
                    'res_model': 'sale.order',
                    'res_id': self.sale_order_id.id,
                })
                chatter_attachment_ids.append(letter_attachment.id)

            # Post approval to chatter with all documents in ONE message
            from markupsafe import Markup

            approval_date_str = (self.approval_date or fields.Date.context_today(self)).strftime('%B %d, %Y')
            device_details = f'{approved_count} approved'
            if unapproved_count > 0:
                device_details += f', {unapproved_count} not approved'
            if deduction_count > 0:
                device_details += f', {deduction_count} with deductions'
            
            # Build documents list - show individual file names for screenshots
            docs_items = ''
            if self.approval_letter:
                docs_items += f'<li>Approval Letter: {self.approval_letter_filename}</li>'
            if photos_attached > 0:
                docs_items += f'<li>{photos_attached} approval screenshot(s) attached below</li>'
            docs_html = ''
            if docs_items:
                docs_html = f'<p class="mb-1"><strong>Documents:</strong></p><ul class="mb-0">{docs_items}</ul>'
            
            # Post to chatter with all attachments in one message
            if chatter_attachment_ids:
                self.sale_order_id.message_post(
                    body=Markup(
                        '<div class="alert alert-success" role="alert">'
                        '<h5 class="alert-heading"><i class="fa fa-check-circle"/> Application Approved</h5>'
                        f'<p class="mb-1"><strong>Date:</strong> {approval_date_str}</p>'
                        f'<p class="mb-1"><strong>Devices:</strong> {device_details}</p>'
                        f'{docs_html}'
                        '</div>'
                    ),
                    message_type='notification',
                    subtype_xmlid='mail.mt_note',
                    attachment_ids=chatter_attachment_ids,
                )
            else:
                # No attachments, just post the status update
                self.sale_order_id.message_post(
                    body=Markup(
                        '<div class="alert alert-success" role="alert">'
                        '<h5 class="alert-heading"><i class="fa fa-check-circle"/> Application Approved</h5>'
                        f'<p class="mb-1"><strong>Date:</strong> {approval_date_str}</p>'
                        f'<p class="mb-1"><strong>Devices:</strong> {device_details}</p>'
                        '</div>'
                    ),
                    message_type='notification',
                    subtype_xmlid='mail.mt_note',
                )
        
        # Sync deductions and approval status to existing invoices
        invoices_updated = self._sync_approval_to_invoices(updated_lines)
        
        # Build notification message
        parts = []
        if unapproved_count > 0:
            parts.append(_("%d approved, %d NOT approved (will bill to client)") % (approved_count, unapproved_count))
            msg_type = 'warning'
        else:
            parts.append(_("All %d devices approved") % approved_count)
            msg_type = 'success'
        
        if deduction_count > 0:
            parts.append(_("%d deduction(s) applied") % deduction_count)
        
        if invoices_updated > 0:
            parts.append(_("%d invoice(s) updated") % invoices_updated)
        
        # Add status update note if applicable
        if self.env.context.get('mark_as_approved'):
            parts.append(_("Status updated to Approved"))
        
        message = ". ".join(parts) + "."
        
        # Close the wizard and show notification
        return {
            'type': 'ir.actions.act_window_close',
            'infos': {
                'type': 'ir.actions.client',
                'tag': 'display_notification',
                'params': {
                    'title': _('Application Approved') if self.env.context.get('mark_as_approved') else _('Device Verification Complete'),
                    'message': message,
                    'type': msg_type,
                    'sticky': False,
                }
            }
        }

    def _sync_approval_to_invoices(self, sale_lines):
        """Sync approval status and deductions to existing invoices.
        
        When approval status or deductions change on SO lines:
        - Client Invoice: unapproved items get 100% price, approved items get normal client portion
        - ADP Invoice: unapproved items get removed (price = 0), approved items get normal ADP portion
        
        Returns number of invoices updated.
        """
        if not sale_lines:
            return 0
        
        invoices_updated = set()
        order = self.sale_order_id
        ADPDevice = self.env['fusion.adp.device.code'].sudo()
        
        # Get all non-cancelled invoices for this order
        invoices = order.invoice_ids.filtered(lambda inv: inv.state != 'cancel')
        if not invoices:
            return 0
        
        for so_line in sale_lines:
            # =================================================================
            # CHECK 1: Is this a NON-ADP funded product?
            # =================================================================
            is_non_adp_funded = so_line.product_id.is_non_adp_funded() if so_line.product_id else False
            
            # =================================================================
            # CHECK 2: Does this product have a valid ADP device code?
            # =================================================================
            device_code = so_line._get_adp_device_code()
            is_adp_device = False
            if device_code and not is_non_adp_funded:
                is_adp_device = ADPDevice.search_count([
                    ('device_code', '=', device_code), 
                    ('active', '=', True)
                ]) > 0
            
            is_approved = so_line.x_fc_adp_approved
            
            # Find linked invoice lines
            invoice_lines = self.env['account.move.line'].sudo().search([
                ('sale_line_ids', 'in', so_line.id),
                ('move_id', 'in', invoices.ids),
            ])
            
            for inv_line in invoice_lines:
                invoice = inv_line.move_id
                
                # Update approval and deduction fields on invoice line
                inv_line_vals = {
                    'x_fc_deduction_type': so_line.x_fc_deduction_type,
                    'x_fc_deduction_value': so_line.x_fc_deduction_value,
                    'x_fc_adp_approved': is_approved,
                }
                
                # Check invoice portion type
                portion_type = getattr(invoice, 'x_fc_adp_invoice_portion', '') or ''
                
                # =================================================================
                # INVOICE LINE LOGIC:
                # - Non-ADP items (NON-ADP code OR not in ADP database):
                #   -> Client Invoice: 100% price
                #   -> ADP Invoice: $0 (should not be there)
                # - Unapproved ADP items:
                #   -> Client Invoice: 100% price  
                #   -> ADP Invoice: $0 (excluded)
                # - Approved ADP items:
                #   -> Client Invoice: client portion %
                #   -> ADP Invoice: ADP portion %
                # =================================================================
                
                if portion_type == 'client':
                    if is_non_adp_funded or not is_adp_device:
                        # NON-ADP item: Client pays 100%
                        new_portion = so_line.price_subtotal
                        inv_line_vals['name'] = so_line.name
                    elif is_adp_device and not is_approved:
                        # UNAPPROVED ADP device: Client pays 100%
                        new_portion = so_line.price_subtotal
                        inv_line_vals['name'] = f"{so_line.name} [NOT APPROVED - 100% Client]"
                    else:
                        # Normal client portion (approved ADP item)
                        new_portion = so_line.x_fc_client_portion
                        inv_line_vals['name'] = so_line.name
                elif portion_type == 'adp':
                    if is_non_adp_funded or not is_adp_device:
                        # NON-ADP item: Remove from ADP invoice (set to 0)
                        new_portion = 0
                        inv_line_vals['name'] = f"{so_line.name} [NON-ADP - Excluded]"
                    elif is_adp_device and not is_approved:
                        # UNAPPROVED ADP device: Remove from ADP invoice (set to 0)
                        new_portion = 0
                        inv_line_vals['name'] = f"{so_line.name} [NOT APPROVED - Excluded]"
                    else:
                        # Normal ADP portion
                        new_portion = so_line.x_fc_adp_portion
                        inv_line_vals['name'] = so_line.name
                else:
                    # Unknown type - just update fields, skip price recalc
                    inv_line.write(inv_line_vals)
                    invoices_updated.add(invoice.id)
                    continue
                
                # Calculate new unit price
                if so_line.product_uom_qty > 0:
                    new_unit_price = new_portion / so_line.product_uom_qty
                else:
                    new_unit_price = 0
                
                inv_line_vals['price_unit'] = new_unit_price
                
                # Need to handle draft vs posted invoices differently
                if invoice.state == 'draft':
                    inv_line.write(inv_line_vals)
                else:
                    # For posted invoices, reset to draft first
                    try:
                        invoice.button_draft()
                        inv_line.write(inv_line_vals)
                        invoice.action_post()
                        _logger.info(f"Reset and updated invoice {invoice.name} for approval/deduction change")
                    except Exception as e:
                        _logger.warning(f"Could not update posted invoice {invoice.name}: {e}")
                
                invoices_updated.add(invoice.id)
        
        return len(invoices_updated)

    def action_approve_all(self):
        """Approve all devices."""
        self.ensure_one()
        
        # Write to lines to ensure proper update
        self.line_ids.write({'approved': True})
        self.write({'all_approved': True})
        
        # Return action to refresh the wizard view
        return {
            'type': 'ir.actions.act_window',
            'res_model': self._name,
            'res_id': self.id,
            'view_mode': 'form',
            'target': 'new',
            'context': self.env.context,
        }


class DeviceApprovalWizardLine(models.TransientModel):
    """Lines for the device approval wizard."""
    _name = 'fusion_claims.device.approval.wizard.line'
    _description = 'Device Approval Wizard Line'

    wizard_id = fields.Many2one(
        'fusion_claims.device.approval.wizard',
        string='Wizard',
        required=True,
        ondelete='cascade',
    )
    sale_line_id = fields.Many2one(
        'sale.order.line',
        string='Sale Line',
        required=True,
    )
    product_name = fields.Char(
        string='Product',
        readonly=True,
    )
    device_code = fields.Char(
        string='Device Code',
        readonly=True,
    )
    device_type = fields.Char(
        string='Device Type',
        readonly=True,
    )
    device_description = fields.Char(
        string='Description',
        readonly=True,
    )
    serial_number = fields.Char(
        string='Serial Number',
        help='Serial number from the sale order line',
    )
    quantity = fields.Float(
        string='Qty',
        readonly=True,
    )
    unit_price = fields.Float(
        string='Unit Price',
        readonly=True,
        digits='Product Price',
    )
    adp_portion = fields.Float(
        string='ADP Portion',
        readonly=True,
        digits='Product Price',
    )
    client_portion = fields.Float(
        string='Client Portion',
        readonly=True,
        digits='Product Price',
    )
    approved = fields.Boolean(
        string='Approved',
        default=True,
        help='Check if this device type was approved by ADP',
    )
    
    # ==========================================================================
    # DEDUCTION FIELDS
    # ==========================================================================
    deduction_type = fields.Selection(
        selection=[
            ('none', 'No Deduction'),
            ('pct', 'Percentage (%)'),
            ('amt', 'Amount ($)'),
        ],
        string='Deduction Type',
        default='none',
        help='Type of ADP deduction. PCT = ADP covers X% of normal. AMT = Fixed $ deducted.',
    )
    deduction_value = fields.Float(
        string='Deduction',
        digits='Product Price',
        help='For PCT: enter percentage (e.g., 75 means ADP covers 75%). For AMT: enter dollar amount.',
    )
    
    # ==========================================================================
    # COMPUTED FIELDS FOR PREVIEW
    # ==========================================================================
    estimated_adp = fields.Float(
        string='Est. ADP',
        compute='_compute_estimated_portions',
        digits='Product Price',
        help='Estimated ADP portion after deduction',
    )
    estimated_client = fields.Float(
        string='Est. Client',
        compute='_compute_estimated_portions',
        digits='Product Price',
        help='Estimated client portion after deduction',
    )

    @api.depends('deduction_type', 'deduction_value', 'adp_portion', 'client_portion', 
                 'unit_price', 'quantity', 'approved')
    def _compute_estimated_portions(self):
        """Compute estimated portions based on current deduction settings."""
        for line in self:
            if not line.approved:
                # If not approved, show 0 for ADP portion
                line.estimated_adp = 0
                line.estimated_client = line.unit_price * line.quantity
                continue
            
            # Get base values from the sale line
            so_line = line.sale_line_id
            if not so_line or not so_line.order_id:
                line.estimated_adp = line.adp_portion
                line.estimated_client = line.client_portion
                continue
            
            # Get client type for base percentages
            client_type = so_line.order_id._get_client_type()
            if client_type == 'REG':
                base_adp_pct = 0.75
            else:
                base_adp_pct = 1.0
            
            # Get ADP price
            adp_price = so_line.x_fc_adp_max_price or line.unit_price
            total = adp_price * line.quantity
            
            # Apply deduction
            if line.deduction_type == 'pct' and line.deduction_value:
                # PCT: ADP only covers deduction_value% of their portion
                effective_pct = base_adp_pct * (line.deduction_value / 100)
                line.estimated_adp = total * effective_pct
                line.estimated_client = total - line.estimated_adp
            elif line.deduction_type == 'amt' and line.deduction_value:
                # AMT: Subtract fixed amount from ADP portion
                base_adp = total * base_adp_pct
                line.estimated_adp = max(0, base_adp - line.deduction_value)
                line.estimated_client = total - line.estimated_adp
            else:
                # No deduction
                line.estimated_adp = total * base_adp_pct
                line.estimated_client = total * (1 - base_adp_pct)
