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

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


class HrT4Summary(models.Model):
    """T4 Summary - One per company per tax year"""
    _name = 'hr.t4.summary'
    _description = 'T4 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 T4 Summary
        position_model = self.env['pdf.field.position']
        return position_model.get_coordinates_dict('T4 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 = ''

    # === Box 88: Number of T4 Slips ===
    slip_count = fields.Integer(
        string='Total T4 Slips (Box 88)',
        compute='_compute_totals',
        store=True,
    )
    slip_ids = fields.One2many(
        'hr.t4.slip',
        'summary_id',
        string='T4 Slips',
    )

    # === Box 14: Employment Income ===
    total_employment_income = fields.Monetary(
        string='Employment Income (Box 14)',
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )

    # === Box 16: Employees CPP Contributions ===
    total_cpp_employee = fields.Monetary(
        string="Employees' CPP (Box 16)",
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )

    # === Box 16A: Employees CPP2 Contributions ===
    total_cpp2_employee = fields.Monetary(
        string="Employees' CPP2 (Box 16A)",
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )

    # === Box 18: Employees EI Premiums ===
    total_ei_employee = fields.Monetary(
        string="Employees' EI (Box 18)",
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )

    # === Box 19: Employer EI Premiums ===
    total_ei_employer = fields.Monetary(
        string="Employer's EI (Box 19)",
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )

    # === Box 22: Income Tax Deducted ===
    total_income_tax = fields.Monetary(
        string='Income Tax (Box 22)',
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )

    # === Box 27: Employer CPP Contributions ===
    total_cpp_employer = fields.Monetary(
        string="Employer's CPP (Box 27)",
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )

    # === Box 27A: Employer CPP2 Contributions ===
    total_cpp2_employer = fields.Monetary(
        string="Employer's CPP2 (Box 27A)",
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )

    # === Box 80: Total Deductions ===
    total_deductions = fields.Monetary(
        string='Total Deductions (Box 80)',
        currency_field='currency_id',
        compute='_compute_totals',
        store=True,
    )

    # === Box 82: Remittances ===
    total_remittances = fields.Monetary(
        string='Total Remittances (Box 82)',
        currency_field='currency_id',
    )

    # === Box 84/86: Overpayment/Balance Due ===
    difference = fields.Monetary(
        string='Difference',
        currency_field='currency_id',
        compute='_compute_difference',
        store=True,
    )
    overpayment = fields.Monetary(
        string='Overpayment (Box 84)',
        currency_field='currency_id',
        compute='_compute_difference',
        store=True,
    )
    balance_due = fields.Monetary(
        string='Balance Due (Box 86)',
        currency_field='currency_id',
        compute='_compute_difference',
        store=True,
    )

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

    # === Filing Information ===
    filing_date = fields.Date(
        string='Filing Date',
        tracking=True,
    )
    xml_file = fields.Binary(
        string='XML File',
        attachment=True,
    )
    xml_filename = fields.Char(
        string='XML Filename',
    )

    # === Box 74: SIN of Proprietor ===
    proprietor_sin = fields.Char(
        string='SIN of Proprietor (Box 74)',
        help='Social Insurance Number of proprietor(s) or principal owner(s)',
    )

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

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

    @api.depends('slip_ids', 'slip_ids.employment_income', 'slip_ids.cpp_employee',
                 'slip_ids.cpp2_employee', 'slip_ids.ei_employee', 'slip_ids.income_tax')
    def _compute_totals(self):
        for rec in self:
            slips = rec.slip_ids
            rec.slip_count = len(slips)
            rec.total_employment_income = sum(slips.mapped('employment_income'))
            rec.total_cpp_employee = sum(slips.mapped('cpp_employee'))
            rec.total_cpp2_employee = sum(slips.mapped('cpp2_employee'))
            rec.total_ei_employee = sum(slips.mapped('ei_employee'))
            rec.total_income_tax = sum(slips.mapped('income_tax'))
            rec.total_cpp_employer = sum(slips.mapped('cpp_employer'))
            rec.total_cpp2_employer = sum(slips.mapped('cpp2_employer'))
            rec.total_ei_employer = sum(slips.mapped('ei_employer'))
            
            # Box 80 = 16 + 16A + 27 + 27A + 18 + 19 + 22
            rec.total_deductions = (
                rec.total_cpp_employee + rec.total_cpp2_employee +
                rec.total_cpp_employer + rec.total_cpp2_employer +
                rec.total_ei_employee + rec.total_ei_employer +
                rec.total_income_tax
            )

    @api.depends('total_deductions', 'total_remittances')
    def _compute_difference(self):
        for rec in self:
            rec.difference = rec.total_deductions - rec.total_remittances
            if rec.difference > 0:
                rec.balance_due = rec.difference
                rec.overpayment = 0
            else:
                rec.overpayment = abs(rec.difference)
                rec.balance_due = 0

    def action_generate_slips(self):
        """Generate T4 slips for all employees with payslips in the tax year"""
        self.ensure_one()

        # Find all employees with confirmed payslips in the year
        year_start = date(self.tax_year, 1, 1)
        year_end = date(self.tax_year, 12, 31)

        payslips = self.env['hr.payslip'].search([
            ('company_id', '=', self.company_id.id),
            ('state', 'in', ['validated', 'paid']),
            ('date_from', '>=', year_start),
            ('date_to', '<=', year_end),
        ])

        if not payslips:
            raise UserError(f'No confirmed payslips found for {self.tax_year}.')

        # Group by employee
        employee_payslips = {}
        for ps in payslips:
            if ps.employee_id.id not in employee_payslips:
                employee_payslips[ps.employee_id.id] = []
            employee_payslips[ps.employee_id.id].append(ps)

        # Delete existing slips
        self.slip_ids.unlink()

        # Create slip for each employee
        T4Slip = self.env['hr.t4.slip']
        Payslip = self.env['hr.payslip']
        
        for employee_id, emp_payslips in employee_payslips.items():
            employee = self.env['hr.employee'].browse(employee_id)
            
            # Sum amounts from payslips
            employment_income = 0
            cpp_ee = cpp_er = cpp2_ee = cpp2_er = 0
            ei_ee = ei_er = 0
            income_tax = 0
            
            # New boxes
            box_40_allowances = 0
            box_42_commissions = 0
            box_44_union_dues = 0

            for ps in emp_payslips:
                # Process each payslip line
                gross_amount = 0
                for line in ps.line_ids:
                    code = line.code or ''
                    category_code = line.category_id.code if line.category_id else None
                    amount = abs(line.total or 0)
                    
                    # Use pay type helpers to identify pay types
                    pay_type = Payslip._get_pay_type_from_code(code, category_code)
                    is_reimbursement = Payslip._is_reimbursement(code, category_code)
                    
                    # Skip reimbursements - they are non-taxable and don't appear on T4
                    if is_reimbursement:
                        continue
                    
                    # Get GROSS amount (this is the total taxable income for Box 14)
                    if code == 'GROSS' and category_code == 'GROSS':
                        gross_amount = amount
                    
                    # Track allowances for Box 40 (these are included in GROSS)
                    # Only count if it's in income categories, not if it's already in GROSS
                    if pay_type == 'allowance' and category_code in ['BASIC', 'ALW']:
                        # Track separately for Box 40, but don't add to employment_income
                        # because GROSS already includes it
                        box_40_allowances += amount
                    
                    # Track commissions for Box 42 (these are included in GROSS)
                    elif pay_type == 'commission' and category_code in ['BASIC', 'ALW']:
                        # Track separately for Box 42, but don't add to employment_income
                        # because GROSS already includes it
                        box_42_commissions += amount
                    
                    # Track union dues for Box 44 (deductions, NOT in GROSS)
                    elif pay_type == 'union_dues' and category_code == 'DED':
                        box_44_union_dues += amount
                        # NOT included in Box 14 (it's a deduction, not income)
                    
                    # Tax deductions
                    elif code == 'CPP_EE':
                        cpp_ee += amount
                    elif code == 'CPP_ER':
                        cpp_er += amount
                    elif code == 'CPP2_EE':
                        cpp2_ee += amount
                    elif code == 'CPP2_ER':
                        cpp2_er += amount
                    elif code == 'EI_EE':
                        ei_ee += amount
                    elif code == 'EI_ER':
                        ei_er += amount
                    elif code in ('FED_TAX', 'PROV_TAX'):
                        income_tax += amount
                
                # Add GROSS to employment income (Box 14)
                # GROSS already includes all taxable income (salary, overtime, bonus, allowances, commissions, etc.)
                employment_income += gross_amount

            # Calculate Box 24 and Box 26
            # In most cases, they equal Box 14, but can differ for exempt amounts
            # For now, set them equal to Box 14 (can be enhanced later for exempt amounts)
            ei_insurable_earnings = employment_income
            cpp_pensionable_earnings = employment_income

            T4Slip.create({
                'summary_id': self.id,
                'employee_id': employee_id,
                'employment_income': employment_income,
                'cpp_employee': cpp_ee,
                'cpp_employer': cpp_er,
                'cpp2_employee': cpp2_ee,
                'cpp2_employer': cpp2_er,
                'ei_employee': ei_ee,
                'ei_employer': ei_er,
                'income_tax': income_tax,
                # Box 24 and 26 - must never be blank per CRA
                'ei_insurable_earnings': ei_insurable_earnings,
                'cpp_pensionable_earnings': cpp_pensionable_earnings,
                # New boxes
                'box_40_taxable_benefits': box_40_allowances,
                'box_42_commissions': box_42_commissions,
                'box_44_union_dues': box_44_union_dues,
            })

        self.state = 'generated'

        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': 'T4 Slips Generated',
                'message': f'Generated {len(employee_payslips)} T4 slips.',
                'type': 'success',
            }
        }

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

    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
        """
        # Determine template type from model name
        template_type = None
        if self._name == 'hr.t4.slip':
            template_type = 'T4'
        elif self._name == 'hr.t4.summary':
            template_type = 'T4 Summary'
        
        if template_type:
            # Query configured positions from database
            position_model = self.env['pdf.field.position']
            return position_model.get_coordinates_dict(template_type)
        
        # Fallback: return empty dict if template type not recognized
        return {}
    
    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:
            # Determine template type for error message
            template_type = 'T4'
            if self._name == 'hr.t4.summary':
                template_type = 'T4 Summary'
            raise UserError(
                f'Text coordinates not configured for {template_type} 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())
    
    def action_fill_pdf(self):
        """Fill the T4 Summary PDF form with data from this summary"""
        self.ensure_one()
        
        try:
            # Try to import pdfrw (preferred) or PyPDF2
            try:
                from pdfrw import PdfReader, PdfWriter, PdfDict
                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
            module_path = os.path.dirname(os.path.dirname(__file__))
            template_path = os.path.join(module_path, 'static', 'pdf', 't4sum-fill-25e.pdf')
            
            # Try in module root directory (fallback)
            if not os.path.exists(template_path):
                template_path = os.path.join(module_path, 't4sum-fill-25e.pdf')
            
            # Try using tools.file_path (Odoo 19)
            if not os.path.exists(template_path):
                try:
                    template_path = tools.file_path('fusion_payroll/static/pdf/t4sum-fill-25e.pdf')
                except:
                    pass
            
            if not os.path.exists(template_path):
                try:
                    template_path = tools.file_path('fusion_payroll/t4sum-fill-25e.pdf')
                except:
                    pass
            
            if not os.path.exists(template_path):
                raise UserError(
                    'T4 Summary PDF template not found. Please ensure t4sum-fill-25e.pdf is in one of these locations:\n'
                    f'1. {os.path.join(module_path, "static", "pdf", "t4sum-fill-25e.pdf")} (recommended)\n'
                    f'2. {os.path.join(module_path, "t4sum-fill-25e.pdf")} (module root)\n\n'
                    'The system will automatically fill the PDF with data from this T4 Summary 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
                try:
                    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
                except (ValueError, TypeError, Exception) as e:
                    # pdfrw failed to parse the PDF, fall back to PyPDF2
                    import logging
                    _logger = logging.getLogger(__name__)
                    _logger.warning(f'pdfrw failed to read PDF ({str(e)}), falling back to PyPDF2')
                    use_pdfrw = False
                    # Import PyPDF2 for fallback
                    try:
                        import PyPDF2
                    except ImportError:
                        raise UserError(
                            f'pdfrw failed to read the PDF file, and PyPDF2 is not available.\n'
                            f'Error: {str(e)}\n\n'
                            f'Please install PyPDF2: pip install PyPDF2'
                        )
            
            if not use_pdfrw:
                # Use PyPDF2 (fallback)
                with open(template_path, 'rb') as template_file:
                    reader = PyPDF2.PdfReader(template_file)
                    writer = PyPDF2.PdfWriter()
                    
                    # Copy pages
                    try:
                        for page in reader.pages:
                            writer.add_page(page)
                    except Exception as page_error:
                        # Check if PyCryptodome is needed
                        try:
                            import Cryptodome
                        except ImportError:
                            raise UserError(
                                f'PDF file appears to be encrypted and requires PyCryptodome for decryption.\n'
                                f'Error: {str(page_error)}\n\n'
                                f'Please install PyCryptodome: pip install pycryptodome\n'
                                f'Or use an unencrypted PDF template.'
                            )
                        raise
                    
                    # Fill form fields (PyPDF2 approach)
                    if '/AcroForm' in reader.trailer['/Root']:
                        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
            company_name = (self.company_id.name or 'Company').replace(" ", "_").replace("/", "_")[:20]
            filename = f'T4Summary_{self.tax_year}_{company_name}.pdf'
            
            # Save filled PDF
            self.write({
                'filled_pdf': pdf_data,
                'filled_pdf_filename': filename,
            })
            
            # Create attachment
            attachment = self.env['ir.attachment'].create({
                'name': filename,
                'type': 'binary',
                'datas': pdf_data,
                'res_model': self._name,
                'res_id': self.id,
                'mimetype': 'application/pdf',
            })
            
            # Post to chatter
            self.message_post(
                body=f'T4 Summary PDF generated: <strong>{filename}</strong>',
                attachment_ids=[attachment.id],
            )
            
            return {
                'type': 'ir.actions.client',
                'tag': 'display_notification',
                'params': {
                    'title': 'PDF Generated',
                    'message': f'T4 Summary PDF filled and saved: {filename}',
                    'type': 'success',
                }
            }
            
        except Exception as e:
            import traceback
            error_msg = f'Error filling PDF: {str(e)}\n\nTraceback:\n{traceback.format_exc()}'
            raise UserError(error_msg)

    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
        module_path = os.path.dirname(os.path.dirname(__file__))
        template_path = os.path.join(module_path, 'static', 'pdf', 't4sum-fill-25e.pdf')
        
        if not os.path.exists(template_path):
            template_path = os.path.join(module_path, 't4sum-fill-25e.pdf')
        
        if not os.path.exists(template_path):
            try:
                template_path = tools.file_path('fusion_payroll/static/pdf/t4sum-fill-25e.pdf')
            except:
                template_path = None
        
        if not template_path or not os.path.exists(template_path):
            raise UserError('T4 Summary PDF template not found.')
        
        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 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.t4.summary/{self.id}/filled_pdf/{self.filled_pdf_filename}?download=true',
            'target': 'self',
        }

    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
        # 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)
        mapping['TaxYear'] = str(self.tax_year)
        mapping['YearEnding'] = str(self.tax_year)
        
        # Payer/Employer information
        company = self.company_id
        if company:
            mapping['PayerName'] = company.name or ''
            mapping['PayerName1'] = company.name or ''
            mapping['EmployerName'] = company.name or ''
            if company.street:
                mapping['PayerAddress1'] = company.street
                mapping['EmployerAddress1'] = company.street
            if company.street2:
                mapping['PayerAddress2'] = company.street2
                mapping['EmployerAddress2'] = company.street2
            if company.city:
                mapping['PayerCity'] = company.city
                mapping['EmployerCity'] = company.city
            if company.state_id:
                mapping['PayerProvince'] = company.state_id.code
                mapping['EmployerProvince'] = company.state_id.code
            if company.zip:
                mapping['PayerPostalCode'] = company.zip
                mapping['EmployerPostalCode'] = company.zip
            
            # Employer account number (Box 54)
            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
            mapping['EmployerAccount'] = account_num
            mapping['AccountNumber'] = account_num
        
        # Box 88: Total number of T4 slips
        if self.slip_count:
            mapping['Box88'] = str(self.slip_count)
            mapping['088'] = str(self.slip_count)
            mapping['TotalSlips'] = str(self.slip_count)
        
        # Box 14: Employment Income
        if self.total_employment_income:
            mapping['Box14'] = f"{self.total_employment_income:.2f}"
            mapping['014'] = f"{self.total_employment_income:.2f}"
            mapping['EmploymentIncome'] = f"{self.total_employment_income:.2f}"
        
        # Box 16: CPP Employee
        if self.total_cpp_employee:
            mapping['Box16'] = f"{self.total_cpp_employee:.2f}"
            mapping['016'] = f"{self.total_cpp_employee:.2f}"
            mapping['CPPEmployee'] = f"{self.total_cpp_employee:.2f}"
        
        # Box 16A: CPP2 Employee
        if self.total_cpp2_employee:
            mapping['Box16A'] = f"{self.total_cpp2_employee:.2f}"
            mapping['016A'] = f"{self.total_cpp2_employee:.2f}"
            mapping['CPP2Employee'] = f"{self.total_cpp2_employee:.2f}"
        
        # Box 18: EI Employee
        if self.total_ei_employee:
            mapping['Box18'] = f"{self.total_ei_employee:.2f}"
            mapping['018'] = f"{self.total_ei_employee:.2f}"
            mapping['EIEmployee'] = f"{self.total_ei_employee:.2f}"
        
        # Box 19: EI Employer
        if self.total_ei_employer:
            mapping['Box19'] = f"{self.total_ei_employer:.2f}"
            mapping['019'] = f"{self.total_ei_employer:.2f}"
            mapping['EIEmployer'] = f"{self.total_ei_employer:.2f}"
        
        # Box 22: Income Tax
        if self.total_income_tax:
            mapping['Box22'] = f"{self.total_income_tax:.2f}"
            mapping['022'] = f"{self.total_income_tax:.2f}"
            mapping['IncomeTax'] = f"{self.total_income_tax:.2f}"
        
        # Box 27: CPP Employer
        if self.total_cpp_employer:
            mapping['Box27'] = f"{self.total_cpp_employer:.2f}"
            mapping['027'] = f"{self.total_cpp_employer:.2f}"
            mapping['CPPEmployer'] = f"{self.total_cpp_employer:.2f}"
        
        # Box 27A: CPP2 Employer
        if self.total_cpp2_employer:
            mapping['Box27A'] = f"{self.total_cpp2_employer:.2f}"
            mapping['027A'] = f"{self.total_cpp2_employer:.2f}"
            mapping['CPP2Employer'] = f"{self.total_cpp2_employer:.2f}"
        
        # Box 80: Total Deductions
        if self.total_deductions:
            mapping['Box80'] = f"{self.total_deductions:.2f}"
            mapping['080'] = f"{self.total_deductions:.2f}"
            mapping['TotalDeductions'] = f"{self.total_deductions:.2f}"
        
        # Box 82: Remittances
        if self.total_remittances:
            mapping['Box82'] = f"{self.total_remittances:.2f}"
            mapping['082'] = f"{self.total_remittances:.2f}"
            mapping['Remittances'] = f"{self.total_remittances:.2f}"
        
        # Box 84: Overpayment
        if self.overpayment:
            mapping['Box84'] = f"{self.overpayment:.2f}"
            mapping['084'] = f"{self.overpayment:.2f}"
            mapping['Overpayment'] = f"{self.overpayment:.2f}"
        
        # Box 86: Balance Due
        if self.balance_due:
            mapping['Box86'] = f"{self.balance_due:.2f}"
            mapping['086'] = f"{self.balance_due:.2f}"
            mapping['BalanceDue'] = f"{self.balance_due:.2f}"
        
        # Box 74: SIN of Proprietor
        if self.proprietor_sin:
            sin_clean = self.proprietor_sin.replace('-', '').replace(' ', '')
            mapping['Box74'] = sin_clean
            mapping['074'] = sin_clean
            mapping['ProprietorSIN'] = sin_clean
        
        # Box 76: Contact Name
        if self.contact_name:
            mapping['Box76'] = self.contact_name
            mapping['076'] = self.contact_name
            mapping['ContactName'] = self.contact_name
        
        # Box 78: Contact Phone
        if self.contact_phone:
            # Parse phone number (format: 905-451-7743 or 9054517743)
            phone_clean = self.contact_phone.replace('-', '').replace(' ', '').replace('(', '').replace(')', '')
            if len(phone_clean) >= 10:
                area_code = phone_clean[:3]
                number = phone_clean[3:]
                mapping['Box78AreaCode'] = area_code
                mapping['Box78Phone'] = number
                mapping['ContactAreaCode'] = area_code
                mapping['ContactPhone'] = number
            mapping['Box78'] = self.contact_phone
            mapping['078'] = self.contact_phone
        
        return mapping


class HrT4Slip(models.Model):
    """T4 Slip - One per employee per tax year"""
    _name = 'hr.t4.slip'
    _description = 'T4 Slip'
    _order = 'employee_id'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    summary_id = fields.Many2one(
        'hr.t4.summary',
        string='T4 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',
    )
    employee_id = fields.Many2one(
        'hr.employee',
        string='Employee',
        required=True,
    )

    # === Employee Information ===
    sin_number = fields.Char(
        string='SIN',
        related='employee_id.sin_number',
    )
    employee_name = fields.Char(
        string='Employee Name',
        related='employee_id.name',
    )

    # === Box 14: Employment Income ===
    employment_income = fields.Monetary(
        string='Employment Income (Box 14)',
        currency_field='currency_id',
    )

    # === Box 16: CPP Contributions ===
    cpp_employee = fields.Monetary(
        string='CPP (Box 16)',
        currency_field='currency_id',
    )

    # === Box 16A: CPP2 Contributions ===
    cpp2_employee = fields.Monetary(
        string='CPP2 (Box 16A)',
        currency_field='currency_id',
    )

    # === Box 18: EI Premiums ===
    ei_employee = fields.Monetary(
        string='EI (Box 18)',
        currency_field='currency_id',
    )

    # === Box 22: Income Tax Deducted ===
    income_tax = fields.Monetary(
        string='Income Tax (Box 22)',
        currency_field='currency_id',
    )

    # === Box 24: EI Insurable Earnings ===
    ei_insurable_earnings = fields.Monetary(
        string='EI Insurable Earnings (Box 24)',
        currency_field='currency_id',
    )

    # === Box 26: CPP Pensionable Earnings ===
    cpp_pensionable_earnings = fields.Monetary(
        string='CPP Pensionable Earnings (Box 26)',
        currency_field='currency_id',
    )

    # === Employer portions (for reference) ===
    cpp_employer = fields.Monetary(
        string='CPP Employer',
        currency_field='currency_id',
    )
    cpp2_employer = fields.Monetary(
        string='CPP2 Employer',
        currency_field='currency_id',
    )
    ei_employer = fields.Monetary(
        string='EI Employer',
        currency_field='currency_id',
    )

    # === T4 Dental Benefits Code ===
    t4_dental_code = fields.Selection(
        related='employee_id.t4_dental_code',
        string='Dental Benefits Code',
    )

    # === Box 40: Other Taxable Allowances and Benefits ===
    box_40_taxable_benefits = fields.Monetary(
        string='Box 40: Other Taxable Allowances/Benefits',
        currency_field='currency_id',
        help='Taxable allowances and benefits in cash (also included in Box 14)',
    )

    # === Box 42: Employment Commissions ===
    box_42_commissions = fields.Monetary(
        string='Box 42: Employment Commissions',
        currency_field='currency_id',
        help='Commission earnings (also included in Box 14)',
    )

    # === Box 44: Union Dues ===
    box_44_union_dues = fields.Monetary(
        string='Box 44: Union Dues',
        currency_field='currency_id',
        help='Union dues deducted (pre-tax deduction, not included in Box 14)',
    )

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

    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
        """
        # Determine template type from model name
        template_type = None
        if self._name == 'hr.t4.slip':
            template_type = 'T4'
        elif self._name == 'hr.t4.summary':
            template_type = 'T4 Summary'
        
        if template_type:
            # Query configured positions from database
            position_model = self.env['pdf.field.position']
            return position_model.get_coordinates_dict(template_type)
        
        # Fallback: return empty dict if template type not recognized
        return {}
    
    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 reportlab.lib.pagesizes import letter
            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:
            # Determine template type for error message
            template_type = 'T4'
            if self._name == 'hr.t4.summary':
                template_type = 'T4 Summary'
            raise UserError(
                f'Text coordinates not configured for {template_type} 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
        template_file.seek(0) if hasattr(template_file, 'seek') else None
        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())
    
    def action_fill_pdf(self):
        """Fill the T4 PDF form with data from this slip"""
        self.ensure_one()
        
        try:
            # Try to import pdfrw (preferred) or PyPDF2
            try:
                from pdfrw import PdfReader, PdfWriter, PdfDict
                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', 't4-fill-25e.pdf')
            
            # 2. Try in module root directory (fallback)
            if not os.path.exists(template_path):
                template_path = os.path.join(module_path, 't4-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/t4-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/t4-fill-25e.pdf')
                except:
                    pass
            
            if not os.path.exists(template_path):
                raise UserError(
                    'T4 PDF template not found. Please ensure t4-fill-25e.pdf is in one of these locations:\n'
                    f'1. {os.path.join(module_path, "static", "pdf", "t4-fill-25e.pdf")} (recommended)\n'
                    f'2. {os.path.join(module_path, "t4-fill-25e.pdf")} (module root)\n\n'
                    'The system will automatically fill the PDF with data from this T4 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
                try:
                    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
                except (ValueError, TypeError, Exception) as e:
                    # pdfrw failed to parse the PDF, fall back to PyPDF2
                    import logging
                    _logger = logging.getLogger(__name__)
                    _logger.warning(f'pdfrw failed to read PDF ({str(e)}), falling back to PyPDF2')
                    use_pdfrw = False
                    # Import PyPDF2 for fallback
                    try:
                        import PyPDF2
                    except ImportError:
                        raise UserError(
                            f'pdfrw failed to read the PDF file, and PyPDF2 is not available.\n'
                            f'Error: {str(e)}\n\n'
                            f'Please install PyPDF2: pip install PyPDF2'
                        )
            
            if not use_pdfrw:
                # Use PyPDF2 (fallback)
                with open(template_path, 'rb') as template_file:
                    reader = PyPDF2.PdfReader(template_file)
                    writer = PyPDF2.PdfWriter()
                    
                    # Copy pages
                    try:
                        for page in reader.pages:
                            writer.add_page(page)
                    except Exception as page_error:
                        # Check if PyCryptodome is needed
                        try:
                            from Crypto.Cipher import AES
                        except ImportError:
                            raise UserError(
                                f'PDF file appears to be encrypted and requires PyCryptodome for decryption.\n'
                                f'Error: {str(page_error)}\n\n'
                                f'Please install PyCryptodome: pip install pycryptodome\n'
                                f'Or use an unencrypted PDF template.'
                            )
                        raise
                    
                    # Fill form fields (PyPDF2 approach)
                    if '/AcroForm' in reader.trailer['/Root']:
                        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
            employee_safe = (self.employee_name or 'Employee').replace(' ', '_').replace(',', '').replace('/', '_').replace('\\', '_')[:30]
            filename = f'T4_{self.tax_year}_{employee_safe}.pdf'
            
            # Save filled PDF
            self.write({
                'filled_pdf': pdf_data,
                'filled_pdf_filename': filename,
            })
            
            # Create attachment
            attachment = self.env['ir.attachment'].create({
                'name': filename,
                'type': 'binary',
                'datas': pdf_data,
                'res_model': self._name,
                'res_id': self.id,
                'mimetype': 'application/pdf',
            })
            
            # Post to chatter
            self.message_post(
                body=f'T4 PDF generated: <strong>{filename}</strong>',
                attachment_ids=[attachment.id],
            )
            
            return {
                'type': 'ir.actions.client',
                'tag': 'display_notification',
                'params': {
                    'title': 'PDF Generated',
                    'message': f'T4 PDF filled and saved: {filename}',
                    'type': 'success',
                }
            }
            
        except Exception as e:
            import traceback
            error_msg = f'Error filling PDF: {str(e)}\n\nTraceback:\n{traceback.format_exc()}'
            raise UserError(error_msg)

    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', 't4-fill-25e.pdf')
        
        if not os.path.exists(template_path):
            template_path = os.path.join(module_path, 't4-fill-25e.pdf')
        
        if not os.path.exists(template_path):
            try:
                template_path = tools.file_path('fusion_payroll/static/pdf/t4-fill-25e.pdf')
            except:
                template_path = None
        
        if not template_path or not os.path.exists(template_path):
            raise UserError('T4 PDF template not found. Please ensure t4-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 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.t4.slip/{self.id}/filled_pdf/{self.filled_pdf_filename}?download=true',
            'target': 'self',
        }

    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
        # 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)
        mapping['TaxYear'] = str(self.tax_year)
        
        # Payer information (from company)
        company = self.company_id
        if company:
            mapping['PayerName'] = company.name or ''
            mapping['PayerName1'] = company.name or ''
            mapping['EmployerName'] = company.name or ''
            if company.street:
                mapping['PayerAddress1'] = company.street
                mapping['EmployerAddress1'] = company.street
            if company.street2:
                mapping['PayerAddress2'] = company.street2
                mapping['EmployerAddress2'] = 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
                mapping['EmployerCity'] = city_line
            
            # Payer account number (Box 54)
            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
            mapping['EmployerAccount'] = account_num
        
        # Employee information
        employee = self.employee_id
        if employee:
            # Employee name - format: Last name, First name
            if employee.name:
                name_parts = employee.name.split(',') if ',' in employee.name else employee.name.split()
                if len(name_parts) >= 2:
                    mapping['LastName'] = name_parts[-1].strip()
                    mapping['FirstName'] = ' '.join(name_parts[:-1]).strip()
                    mapping['EmployeeLastName'] = name_parts[-1].strip()
                    mapping['EmployeeFirstName'] = ' '.join(name_parts[:-1]).strip()
                else:
                    mapping['LastName'] = employee.name
                    mapping['EmployeeLastName'] = employee.name
            
            # Employee address (using private_* fields in Odoo 19)
            # Use getattr to safely access fields that may not exist
            private_street = getattr(employee, 'private_street', None) or ''
            private_street2 = getattr(employee, 'private_street2', None) or ''
            private_city = getattr(employee, 'private_city', None) or ''
            private_state_id = getattr(employee, 'private_state_id', None)
            private_zip = getattr(employee, 'private_zip', None) or ''
            
            if private_street or private_city:
                if private_street:
                    mapping['EmployeeAddress1'] = private_street
                if private_street2:
                    mapping['EmployeeAddress2'] = private_street2
                if private_city:
                    city_line = private_city
                    if private_state_id:
                        city_line += f", {private_state_id.code}"
                    if private_zip:
                        city_line += f" {private_zip}"
                    mapping['EmployeeCity'] = city_line
        
        # SIN (Box 12)
        if self.sin_number:
            sin_clean = self.sin_number.replace('-', '').replace(' ', '')
            mapping['SIN'] = sin_clean
            mapping['Box12'] = sin_clean
            mapping['SocialInsuranceNumber'] = sin_clean
        
        # Province of employment (Box 10) - from employee's work location or province
        if employee and employee.address_id and employee.address_id.state_id:
            mapping['Box10'] = employee.address_id.state_id.code
            mapping['Province'] = employee.address_id.state_id.code
        elif company and company.state_id:
            mapping['Box10'] = company.state_id.code
            mapping['Province'] = company.state_id.code
        
        # Box 14: Employment Income
        if self.employment_income:
            mapping['Box14'] = f"{self.employment_income:.2f}"
            mapping['014'] = f"{self.employment_income:.2f}"
            mapping['EmploymentIncome'] = f"{self.employment_income:.2f}"
        
        # Box 16: CPP Employee
        if self.cpp_employee:
            mapping['Box16'] = f"{self.cpp_employee:.2f}"
            mapping['016'] = f"{self.cpp_employee:.2f}"
            mapping['CPPEmployee'] = f"{self.cpp_employee:.2f}"
        
        # Box 16A: CPP2 Employee
        if self.cpp2_employee:
            mapping['Box16A'] = f"{self.cpp2_employee:.2f}"
            mapping['016A'] = f"{self.cpp2_employee:.2f}"
            mapping['CPP2Employee'] = f"{self.cpp2_employee:.2f}"
        
        # Box 18: EI Employee
        if self.ei_employee:
            mapping['Box18'] = f"{self.ei_employee:.2f}"
            mapping['018'] = f"{self.ei_employee:.2f}"
            mapping['EIEmployee'] = f"{self.ei_employee:.2f}"
        
        # Box 22: Income Tax
        if self.income_tax:
            mapping['Box22'] = f"{self.income_tax:.2f}"
            mapping['022'] = f"{self.income_tax:.2f}"
            mapping['IncomeTax'] = f"{self.income_tax:.2f}"
        
        # Box 24: EI Insurable Earnings
        if self.ei_insurable_earnings:
            mapping['Box24'] = f"{self.ei_insurable_earnings:.2f}"
            mapping['024'] = f"{self.ei_insurable_earnings:.2f}"
            mapping['EIInsurableEarnings'] = f"{self.ei_insurable_earnings:.2f}"
        
        # Box 26: CPP Pensionable Earnings
        if self.cpp_pensionable_earnings:
            mapping['Box26'] = f"{self.cpp_pensionable_earnings:.2f}"
            mapping['026'] = f"{self.cpp_pensionable_earnings:.2f}"
            mapping['CPPPensionableEarnings'] = f"{self.cpp_pensionable_earnings:.2f}"
        
        # Box 40: Taxable Benefits
        if self.box_40_taxable_benefits:
            mapping['Box40'] = f"{self.box_40_taxable_benefits:.2f}"
            mapping['040'] = f"{self.box_40_taxable_benefits:.2f}"
            mapping['TaxableBenefits'] = f"{self.box_40_taxable_benefits:.2f}"
        
        # Box 42: Commissions
        if self.box_42_commissions:
            mapping['Box42'] = f"{self.box_42_commissions:.2f}"
            mapping['042'] = f"{self.box_42_commissions:.2f}"
            mapping['Commissions'] = f"{self.box_42_commissions:.2f}"
        
        # Box 44: Union Dues
        if self.box_44_union_dues:
            mapping['Box44'] = f"{self.box_44_union_dues:.2f}"
            mapping['044'] = f"{self.box_44_union_dues:.2f}"
            mapping['UnionDues'] = f"{self.box_44_union_dues:.2f}"
        
        # Box 45: Dental Benefits Code
        if self.t4_dental_code:
            mapping['Box45'] = str(self.t4_dental_code)
            mapping['045'] = str(self.t4_dental_code)
            mapping['DentalCode'] = str(self.t4_dental_code)
        
        return mapping
