# -*- coding: utf-8 -*-

import base64
import os
import io
from datetime import date
from odoo import models, fields, api
from odoo.exceptions import UserError
from odoo import tools


class HrT4ASummary(models.Model):
    """T4A Summary - One per company per tax year"""
    _name = 'hr.t4a.summary'
    _description = 'T4A Summary'
    _order = 'tax_year desc'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    def _get_pdf_text_coordinates(self):
        """Get text overlay coordinates for flattened PDF
        Returns dict mapping field names to (x, y, font_size, font_name) tuples
        Coordinates are in points (1/72 inch), origin at bottom-left
        Reads from pdf.field.position model based on template type
        """
        # Query configured positions from database for T4A Summary
        position_model = self.env['pdf.field.position']
        return position_model.get_coordinates_dict('T4A Summary')

    STATE_SELECTION = [
        ('draft', 'Draft'),
        ('generated', 'Generated'),
        ('filed', 'Filed'),
    ]

    name = fields.Char(
        string='Reference',
        compute='_compute_name',
        store=True,
    )
    company_id = fields.Many2one(
        'res.company',
        string='Company',
        required=True,
        default=lambda self: self.env.company,
    )
    currency_id = fields.Many2one(
        related='company_id.currency_id',
    )
    tax_year = fields.Integer(
        string='Tax Year',
        required=True,
        default=lambda self: date.today().year - 1,
    )
    state = fields.Selection(
        selection=STATE_SELECTION,
        string='Status',
        default='draft',
        tracking=True,
    )

    # === CRA Information ===
    cra_business_number = fields.Char(
        string='CRA Business Number',
        compute='_compute_cra_business_number',
        readonly=True,
    )

    @api.depends('company_id')
    def _compute_cra_business_number(self):
        """Get CRA business number from payroll settings."""
        for summary in self:
            if summary.company_id:
                settings = self.env['payroll.config.settings'].get_settings(summary.company_id.id)
                summary.cra_business_number = settings.get_cra_payroll_account_number() or summary.company_id.vat or ''
            else:
                summary.cra_business_number = ''

    # === Slip Count ===
    slip_count = fields.Integer(
        string='Total T4A Slips',
        compute='_compute_totals',
        store=True,
    )
    slip_ids = fields.One2many(
        'hr.t4a.slip',
        'summary_id',
        string='T4A Slips',
    )

    # === Summary Totals ===
    total_box_016 = fields.Monetary(
        string='Total Box 016 (Pension)',
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )
    total_box_018 = fields.Monetary(
        string='Total Box 018 (Lump-Sum)',
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )
    total_box_020 = fields.Monetary(
        string='Total Box 020 (Commissions)',
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )
    total_box_024 = fields.Monetary(
        string='Total Box 024 (Annuities)',
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )
    total_box_048 = fields.Monetary(
        string='Total Box 048 (Fees)',
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )

    # === Contact Information ===
    contact_name = fields.Char(
        string='Contact Person',
        default=lambda self: self.env.user.name,
    )
    contact_phone = fields.Char(
        string='Telephone',
    )

    # === Filing Information ===
    filing_date = fields.Date(
        string='Filing Date',
        tracking=True,
    )

    @api.depends('tax_year', 'company_id')
    def _compute_name(self):
        for rec in self:
            rec.name = f"T4A Summary {rec.tax_year} - {rec.company_id.name}"

    @api.depends('slip_ids')
    def _compute_totals(self):
        for rec in self:
            slips = rec.slip_ids
            rec.slip_count = len(slips)
            rec.total_box_016 = sum(slips.mapped('box_016_pension'))
            rec.total_box_018 = sum(slips.mapped('box_018_lump_sum'))
            rec.total_box_020 = sum(slips.mapped('box_020_commissions'))
            rec.total_box_024 = sum(slips.mapped('box_024_annuities'))
            rec.total_box_048 = sum(slips.mapped('box_048_fees'))

    def action_mark_filed(self):
        """Mark T4A Summary as filed"""
        self.ensure_one()
        self.write({
            'state': 'filed',
            'filing_date': date.today(),
        })


class HrT4ASlip(models.Model):
    """T4A Slip - One per recipient per tax year"""
    _name = 'hr.t4a.slip'
    _description = 'T4A Slip'
    _order = 'recipient_name'

    def _get_pdf_text_coordinates(self):
        """Get text overlay coordinates for flattened PDF
        Returns dict mapping field names to (x, y, font_size, font_name) tuples
        Coordinates are in points (1/72 inch), origin at bottom-left
        Reads from pdf.field.position model based on template type
        """
        # Query configured positions from database for T4A
        position_model = self.env['pdf.field.position']
        return position_model.get_coordinates_dict('T4A')
    
    def _overlay_text_on_pdf(self, template_path, field_mapping):
        """Overlay text on a flattened PDF using reportlab
        Returns base64-encoded PDF data
        """
        try:
            from reportlab.pdfgen import canvas
            from PyPDF2 import PdfReader, PdfWriter
        except ImportError as e:
            raise UserError(f'Required library not available: {str(e)}\nPlease install reportlab and PyPDF2.')
        
        # Get text coordinates
        text_coords = self._get_pdf_text_coordinates()
        if not text_coords:
            raise UserError(
                'Text coordinates not configured for T4A template. '
                'Please configure PDF field positions in Payroll → Configuration → PDF Field Positions.'
            )
        
        # Read the template PDF
        with open(template_path, 'rb') as template_file:
            template_reader = PdfReader(template_file)
            if not template_reader.pages:
                raise UserError('Template PDF has no pages')
            
            # Get first page dimensions
            first_page = template_reader.pages[0]
            page_width = float(first_page.mediabox.width)
            page_height = float(first_page.mediabox.height)
        
        # Create overlay PDF with text
        overlay_buffer = io.BytesIO()
        can = canvas.Canvas(overlay_buffer, pagesize=(page_width, page_height))
        
        # Draw text for each field
        for field_name, value in field_mapping.items():
            if field_name in text_coords and value:
                coord_data = text_coords[field_name]
                # Handle both old format (x, y, font_size) and new format (x, y, font_size, font_name)
                if len(coord_data) == 4:
                    x, y, font_size, font_name = coord_data
                elif len(coord_data) == 3:
                    x, y, font_size = coord_data
                    font_name = 'Helvetica'  # Default font
                else:
                    continue  # Skip invalid coordinate data
                
                can.setFont(font_name, font_size)
                can.drawString(x, y, str(value))
        
        can.save()
        overlay_buffer.seek(0)
        
        # Merge overlay with template
        with open(template_path, 'rb') as template_file:
            template_reader = PdfReader(template_file)
            overlay_reader = PdfReader(overlay_buffer)
            
            writer = PdfWriter()
            for i, page in enumerate(template_reader.pages):
                if i < len(overlay_reader.pages):
                    page.merge_page(overlay_reader.pages[i])
                writer.add_page(page)
            
            # Write to bytes
            output_buffer = io.BytesIO()
            writer.write(output_buffer)
            return base64.b64encode(output_buffer.getvalue())

    summary_id = fields.Many2one(
        'hr.t4a.summary',
        string='T4A Summary',
        required=True,
        ondelete='cascade',
    )
    company_id = fields.Many2one(
        related='summary_id.company_id',
    )
    currency_id = fields.Many2one(
        related='summary_id.currency_id',
    )
    tax_year = fields.Integer(
        related='summary_id.tax_year',
        store=True,
    )

    # === Recipient Information ===
    recipient_id = fields.Many2one(
        'res.partner',
        string='Recipient',
        help='Recipient partner (individual or business)',
    )
    recipient_name = fields.Char(
        string='Recipient Name',
        required=True,
        help='Last name, first name and initials',
    )
    recipient_address = fields.Text(
        string='Recipient Address',
        help='Full address including province and postal code',
    )
    recipient_sin = fields.Char(
        string='SIN (Box 12)',
        help='Social Insurance Number (9 digits)',
    )
    recipient_account_number = fields.Char(
        string='Account Number (Box 13)',
        help='Business Number if recipient is a business',
    )

    # === Income Boxes ===
    box_016_pension = fields.Monetary(
        string='Box 016: Pension or Superannuation',
        currency_field='currency_id',
    )
    box_018_lump_sum = fields.Monetary(
        string='Box 018: Lump-Sum Payments',
        currency_field='currency_id',
    )
    box_020_commissions = fields.Monetary(
        string='Box 020: Self-Employed Commissions',
        currency_field='currency_id',
    )
    box_024_annuities = fields.Monetary(
        string='Box 024: Annuities',
        currency_field='currency_id',
    )
    box_048_fees = fields.Monetary(
        string='Box 048: Fees for Services',
        currency_field='currency_id',
    )

    # === Other Information (028-197) ===
    other_info_ids = fields.One2many(
        'hr.t4a.other.info',
        'slip_id',
        string='Other Information',
        help='Other information boxes (028-197)',
    )

    # === PDF Generation ===
    filled_pdf = fields.Binary(
        string='Filled PDF',
        attachment=True,
    )
    filled_pdf_filename = fields.Char(
        string='PDF Filename',
    )

    @api.onchange('recipient_id')
    def _onchange_recipient_id(self):
        """Auto-fill recipient information from partner"""
        if self.recipient_id:
            # Format name: Last name, First name
            name_parts = self.recipient_id.name.split(',') if ',' in self.recipient_id.name else self.recipient_id.name.split()
            if len(name_parts) >= 2:
                self.recipient_name = f"{name_parts[-1].strip()}, {' '.join(name_parts[:-1]).strip()}"
            else:
                self.recipient_name = self.recipient_id.name
            
            # Build address
            address_parts = []
            if self.recipient_id.street:
                address_parts.append(self.recipient_id.street)
            if self.recipient_id.street2:
                address_parts.append(self.recipient_id.street2)
            if self.recipient_id.city:
                city_line = self.recipient_id.city
                if self.recipient_id.state_id:
                    city_line += f", {self.recipient_id.state_id.code}"
                if self.recipient_id.zip:
                    city_line += f" {self.recipient_id.zip}"
                address_parts.append(city_line)
            self.recipient_address = '\n'.join(address_parts)
            
            # Get SIN if available (might be stored in a custom field)
            if hasattr(self.recipient_id, 'sin_number'):
                self.recipient_sin = self.recipient_id.sin_number

    def action_fill_pdf(self):
        """Fill the T4A PDF form with data from this slip"""
        self.ensure_one()
        
        try:
            # Try to import pdfrw (preferred) or PyPDF2
            try:
                from pdfrw import PdfReader, PdfWriter
                use_pdfrw = True
            except ImportError:
                try:
                    import PyPDF2
                    use_pdfrw = False
                except ImportError:
                    raise UserError(
                        'PDF library not found. Please install pdfrw or PyPDF2:\n'
                        'pip install pdfrw\n'
                        'or\n'
                        'pip install PyPDF2'
                    )
            
            # Get PDF template path - try multiple locations
            # 1. Try in static/pdf/ folder (recommended location)
            module_path = os.path.dirname(os.path.dirname(__file__))
            template_path = os.path.join(module_path, 'static', 'pdf', 't4a-fill-25e.pdf')
            
            # 2. Try in module root directory (fallback)
            if not os.path.exists(template_path):
                template_path = os.path.join(module_path, 't4a-fill-25e.pdf')
            
            # 3. Try using tools.file_path (Odoo 19)
            if not os.path.exists(template_path):
                try:
                    template_path = tools.file_path('fusion_payroll/static/pdf/t4a-fill-25e.pdf')
                except:
                    pass
            
            # 4. Final fallback - root directory
            if not os.path.exists(template_path):
                try:
                    template_path = tools.file_path('fusion_payroll/t4a-fill-25e.pdf')
                except:
                    pass
            
            if not os.path.exists(template_path):
                raise UserError(
                    'T4A PDF template not found. Please ensure t4a-fill-25e.pdf is in one of these locations:\n'
                    f'1. {os.path.join(module_path, "static", "pdf", "t4a-fill-25e.pdf")} (recommended)\n'
                    f'2. {os.path.join(module_path, "t4a-fill-25e.pdf")} (module root)\n\n'
                    'The system will automatically fill the PDF with data from this T4A slip when you click "Fill PDF".'
                )
            
            # Get field mapping
            field_mapping = self._get_pdf_field_mapping()
            
            # Check if we should use text overlay (for flattened PDFs)
            text_coords = self._get_pdf_text_coordinates()
            if text_coords:
                # Use text overlay method for flattened PDF
                pdf_data = self._overlay_text_on_pdf(template_path, field_mapping)
            elif use_pdfrw:
                # Use pdfrw to fill PDF
                from pdfrw import PdfDict
                template = PdfReader(template_path)
                
                # Fill form fields
                if hasattr(template.Root, 'AcroForm') and template.Root.AcroForm:
                    if hasattr(template.Root.AcroForm, 'Fields') and template.Root.AcroForm.Fields:
                        for field in template.Root.AcroForm.Fields:
                            # Get field name (can be in /T or /TU)
                            field_name = None
                            if hasattr(field, 'T'):
                                field_name = str(field.T).strip('()')
                            elif hasattr(field, 'TU'):
                                field_name = str(field.TU).strip('()')
                            
                            if field_name and field_name in field_mapping:
                                value = field_mapping[field_name]
                                if value is not None and value != '':
                                    # Set field value
                                    field.V = str(value)
                                    # Make sure field is not read-only
                                    if hasattr(field, 'Ff'):
                                        field.Ff = 0  # Remove read-only flag
                
                # Write filled PDF to temporary file
                import tempfile
                with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
                    tmp_path = tmp_file.name
                
                writer = PdfWriter()
                writer.write(template, tmp_path)
                
                # Read filled PDF
                with open(tmp_path, 'rb') as f:
                    pdf_data = base64.b64encode(f.read())
                
                # Clean up temp file
                try:
                    os.remove(tmp_path)
                except:
                    pass
            else:
                # Use PyPDF2 (fallback)
                with open(template_path, 'rb') as template_file:
                    reader = PyPDF2.PdfReader(template_file)
                    writer = PyPDF2.PdfWriter()
                    
                    # Copy pages
                    for page in reader.pages:
                        writer.add_page(page)
                    
                    # Fill form fields
                    field_mapping = self._get_pdf_field_mapping()
                    if reader.get_form_text_fields():
                        writer.update_page_form_field_values(writer.pages[0], field_mapping)
                    
                    # Write to bytes
                    output_buffer = io.BytesIO()
                    writer.write(output_buffer)
                    pdf_data = base64.b64encode(output_buffer.getvalue())
            
            # Generate filename
            recipient_safe = self.recipient_name.replace(' ', '_').replace(',', '')[:30]
            filename = f'T4A_{self.tax_year}_{recipient_safe}.pdf'
            
            # Save filled PDF
            self.write({
                'filled_pdf': pdf_data,
                'filled_pdf_filename': filename,
            })
            
            # Post to chatter
            self.message_post(
                body=f'T4A PDF generated: <strong>{filename}</strong>',
                attachment_ids=[(0, 0, {
                    'name': filename,
                    'type': 'binary',
                    'datas': pdf_data,
                    'res_model': self._name,
                    'res_id': self.id,
                    'mimetype': 'application/pdf',
                })],
            )
            
            return {
                'type': 'ir.actions.client',
                'tag': 'display_notification',
                'params': {
                    'title': 'PDF Generated',
                    'message': f'T4A PDF filled and saved: {filename}',
                    'type': 'success',
                }
            }
            
        except Exception as e:
            raise UserError(f'Error filling PDF: {str(e)}')

    def action_extract_pdf_fields(self):
        """Helper method to extract PDF form field names (for debugging)"""
        self.ensure_one()
        
        try:
            from pdfrw import PdfReader
        except ImportError:
            raise UserError('pdfrw library not installed. Install with: pip install pdfrw')
        
        # Get PDF template path - try multiple locations
        module_path = os.path.dirname(os.path.dirname(__file__))
        template_path = os.path.join(module_path, 'static', 'pdf', 't4a-fill-25e.pdf')
        
        if not os.path.exists(template_path):
            template_path = os.path.join(module_path, 't4a-fill-25e.pdf')
        
        if not os.path.exists(template_path):
            try:
                template_path = tools.file_path('fusion_payroll/static/pdf/t4a-fill-25e.pdf')
            except:
                template_path = None
        
        if not template_path or not os.path.exists(template_path):
            raise UserError('T4A PDF template not found. Please ensure t4a-fill-25e.pdf is in static/pdf/ or module root.')
        
        template = PdfReader(template_path)
        field_names = []
        
        # Extract field names from all pages
        for page_num, page in enumerate(template.pages, 1):
            if hasattr(page, 'Annots') and page.Annots:
                for annot in page.Annots:
                    if hasattr(annot, 'Subtype') and str(annot.Subtype) == '/Widget':
                        if hasattr(annot, 'T'):
                            field_name = str(annot.T).strip('()')
                            field_names.append(f'Page {page_num}: {field_name}')
        
        # Return as message
        if field_names:
            message = 'PDF Form Fields Found:\n\n' + '\n'.join(field_names[:50])
            if len(field_names) > 50:
                message += f'\n\n... and {len(field_names) - 50} more fields'
        else:
            message = 'No form fields found in PDF. The PDF may not be a fillable form, or field names are stored differently.'
        
        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': 'PDF Fields',
                'message': message,
                'type': 'info',
                'sticky': True,
            }
        }

    def _get_pdf_field_mapping(self):
        """Map model fields to PDF form field names"""
        # This mapping may need to be adjusted based on actual PDF form field names
        # Common field name patterns for T4A forms:
        # You can use action_extract_pdf_fields() to see actual field names in the PDF
        mapping = {}
        
        # Year
        mapping['Year'] = str(self.tax_year)
        mapping['year'] = str(self.tax_year)
        mapping['YEAR'] = str(self.tax_year)
        
        # Payer information (from company)
        company = self.company_id
        if company:
            mapping['PayerName'] = company.name or ''
            mapping['PayerName1'] = company.name or ''
            if company.street:
                mapping['PayerAddress1'] = company.street
            if company.street2:
                mapping['PayerAddress2'] = company.street2
            if company.city:
                city_line = company.city
                if company.state_id:
                    city_line += f", {company.state_id.code}"
                if company.zip:
                    city_line += f" {company.zip}"
                mapping['PayerCity'] = city_line
            
            # Payer account number
            settings = self.env['payroll.config.settings'].get_settings(company.id)
            account_num = settings.get_cra_payroll_account_number() or company.vat or ''
            mapping['PayerAccount'] = account_num
            mapping['Box54'] = account_num
        
        # Recipient information
        if self.recipient_name:
            # Split name into last, first
            name_parts = self.recipient_name.split(',')
            if len(name_parts) >= 2:
                mapping['LastName'] = name_parts[0].strip()
                mapping['FirstName'] = name_parts[1].strip()
            else:
                # Try to split by space
                name_parts = self.recipient_name.split()
                if len(name_parts) >= 2:
                    mapping['LastName'] = name_parts[-1]
                    mapping['FirstName'] = ' '.join(name_parts[:-1])
                else:
                    mapping['LastName'] = self.recipient_name
        
        if self.recipient_address:
            addr_lines = self.recipient_address.split('\n')
            for i, line in enumerate(addr_lines[:3], 1):
                mapping[f'RecipientAddress{i}'] = line
        
        if self.recipient_sin:
            mapping['SIN'] = self.recipient_sin.replace('-', '').replace(' ', '')
            mapping['Box12'] = self.recipient_sin.replace('-', '').replace(' ', '')
        
        if self.recipient_account_number:
            mapping['Box13'] = self.recipient_account_number
        
        # Income boxes
        if self.box_016_pension:
            mapping['Box016'] = f"{self.box_016_pension:.2f}"
            mapping['016'] = f"{self.box_016_pension:.2f}"
        
        if self.box_018_lump_sum:
            mapping['Box018'] = f"{self.box_018_lump_sum:.2f}"
            mapping['018'] = f"{self.box_018_lump_sum:.2f}"
        
        if self.box_020_commissions:
            mapping['Box020'] = f"{self.box_020_commissions:.2f}"
            mapping['020'] = f"{self.box_020_commissions:.2f}"
        
        if self.box_024_annuities:
            mapping['Box024'] = f"{self.box_024_annuities:.2f}"
            mapping['024'] = f"{self.box_024_annuities:.2f}"
        
        if self.box_048_fees:
            mapping['Box048'] = f"{self.box_048_fees:.2f}"
            mapping['048'] = f"{self.box_048_fees:.2f}"
        
        # Other information boxes
        for other_info in self.other_info_ids:
            box_num = str(other_info.box_number).zfill(3)
            mapping[f'Box{box_num}'] = f"{other_info.amount:.2f}"
            mapping[box_num] = f"{other_info.amount:.2f}"
        
        return mapping

    def action_download_pdf(self):
        """Download the filled PDF"""
        self.ensure_one()
        if not self.filled_pdf:
            raise UserError('No PDF has been generated yet. Please click "Fill PDF" first.')
        
        return {
            'type': 'ir.actions.act_url',
            'url': f'/web/content/hr.t4a.slip/{self.id}/filled_pdf/{self.filled_pdf_filename}?download=true',
            'target': 'self',
        }


class HrT4AOtherInfo(models.Model):
    """T4A Other Information (Boxes 028-197)"""
    _name = 'hr.t4a.other.info'
    _description = 'T4A Other Information'
    _order = 'box_number'

    slip_id = fields.Many2one(
        'hr.t4a.slip',
        string='T4A Slip',
        required=True,
        ondelete='cascade',
    )
    box_number = fields.Integer(
        string='Box Number',
        required=True,
        help='Box number (028-197)',
    )
    currency_id_slip = fields.Many2one(
        related='slip_id.currency_id',
        string='Currency',
    )
    amount = fields.Monetary(
        string='Amount',
        currency_field='currency_id_slip',
        required=True,
    )
    description = fields.Char(
        string='Description',
        help='Description of this income type',
    )
