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

import base64
from datetime import date, timedelta
from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError


class HrROE(models.Model):
    _name = 'hr.roe'
    _description = 'Record of Employment'
    _order = 'create_date desc'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    # === ROE Reason Codes (Service Canada) ===
    ROE_REASON_CODES = [
        ('A', 'A - Shortage of work'),
        ('B', 'B - Strike or lockout'),
        ('D', 'D - Illness or injury'),
        ('E', 'E - Quit'),
        ('F', 'F - Maternity'),
        ('G', 'G - Retirement'),
        ('H', 'H - Work-Sharing'),
        ('J', 'J - Apprentice training'),
        ('K', 'K - Other'),
        ('M', 'M - Dismissal'),
        ('N', 'N - Leave of absence'),
        ('P', 'P - Parental'),
        ('Z', 'Z - Compassionate Care/Family Caregiver'),
    ]

    PAY_PERIOD_TYPES = [
        ('W', 'Weekly'),
        ('B', 'Bi-Weekly'),
        ('S', 'Semi-Monthly'),
        ('M', 'Monthly'),
    ]

    STATE_SELECTION = [
        ('draft', 'Draft'),
        ('ready', 'Ready to Submit'),
        ('submitted', 'Submitted'),
        ('archived', 'Archived'),
    ]

    name = fields.Char(
        string='Reference',
        required=True,
        copy=False,
        default=lambda self: self.env['ir.sequence'].next_by_code('hr.roe') or 'New',
    )
    state = fields.Selection(
        selection=STATE_SELECTION,
        string='Status',
        default='draft',
        tracking=True,
    )
    employee_id = fields.Many2one(
        'hr.employee',
        string='Employee',
        required=True,
        tracking=True,
    )
    company_id = fields.Many2one(
        'res.company',
        string='Company',
        required=True,
        default=lambda self: self.env.company,
    )

    # === Box 5: CRA Business Number ===
    cra_business_number = fields.Char(
        string='CRA Business Number (BN)',
        compute='_compute_cra_business_number',
        readonly=True,
        help='15-character format: 123456789RP0001',
    )

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

    # === Box 6: Pay Period Type ===
    pay_period_type = fields.Selection(
        selection=PAY_PERIOD_TYPES,
        string='Pay Period Type',
        compute='_compute_pay_period_type',
        store=True,
    )

    # === Box 8: Social Insurance Number ===
    sin_number = fields.Char(
        string='Social Insurance Number',
        related='employee_id.sin_number',
        readonly=True,
    )

    # === Box 10: First Day Worked ===
    first_day_worked = fields.Date(
        string='First Day Worked',
        related='employee_id.hire_date',
        readonly=True,
    )

    # === Box 11: Last Day for Which Paid ===
    last_day_paid = fields.Date(
        string='Last Day for Which Paid',
        required=True,
        tracking=True,
    )

    # === Box 12: Final Pay Period Ending Date ===
    final_pay_period_end = fields.Date(
        string='Final Pay Period Ending Date',
        required=True,
    )

    # === Box 13: Occupation ===
    occupation = fields.Char(
        string='Occupation',
        related='employee_id.job_title',
        readonly=True,
    )

    # === Box 14: Expected Date of Recall ===
    expected_recall_date = fields.Date(
        string='Expected Date of Recall',
        help='If temporary layoff, when employee is expected to return',
    )

    # === Box 15A: Total Insurable Hours ===
    total_insurable_hours = fields.Float(
        string='Total Insurable Hours',
        digits=(10, 2),
        help='Total hours worked during the insurable period',
    )

    # === Box 15B: Total Insurable Earnings ===
    total_insurable_earnings = fields.Float(
        string='Total Insurable Earnings',
        digits=(10, 2),
        help='Total earnings during the insurable period',
    )

    # === Box 15C: Insurable Earnings by Pay Period ===
    pay_period_earnings_ids = fields.One2many(
        'hr.roe.pay.period',
        'roe_id',
        string='Pay Period Earnings',
    )

    # === Box 16: Reason for Issuing ROE ===
    reason_code = fields.Selection(
        selection=ROE_REASON_CODES,
        string='Reason for Issuing ROE',
        required=True,
        tracking=True,
    )

    # === Box 17: Other Payments ===
    other_payments = fields.Text(
        string='Other Payments/Benefits',
        help='Other than regular pay, paid or payable at a later date',
    )

    # === Box 18: Comments ===
    comments = fields.Text(
        string='Comments',
    )

    # === Box 20: Communication Preference ===
    communication_language = fields.Selection([
        ('E', 'English'),
        ('F', 'French'),
    ], string='Communication Preference', default='E')

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

    # === File Attachments ===
    blk_file = fields.Binary(
        string='BLK File',
        attachment=True,
    )
    blk_filename = fields.Char(
        string='BLK Filename',
    )
    pdf_file = fields.Binary(
        string='PDF File',
        attachment=True,
    )
    pdf_filename = fields.Char(
        string='PDF Filename',
    )

    # === Submission Tracking ===
    submission_date = fields.Date(
        string='Submission Date',
        tracking=True,
    )
    submission_deadline = fields.Date(
        string='Submission Deadline',
        compute='_compute_submission_deadline',
        store=True,
    )
    service_canada_serial = fields.Char(
        string='Service Canada Serial Number',
        help='Serial number assigned after submission',
    )

    @api.depends('employee_id', 'employee_id.pay_schedule')
    def _compute_pay_period_type(self):
        mapping = {
            'weekly': 'W',
            'biweekly': 'B',
            'semi_monthly': 'S',
            'monthly': 'M',
        }
        for roe in self:
            schedule = roe.employee_id.pay_schedule if roe.employee_id else 'biweekly'
            roe.pay_period_type = mapping.get(schedule, 'B')

    @api.depends('last_day_paid')
    def _compute_submission_deadline(self):
        for roe in self:
            if roe.last_day_paid:
                # ROE must be submitted within 5 calendar days
                roe.submission_deadline = roe.last_day_paid + timedelta(days=5)
            else:
                roe.submission_deadline = False

    def action_calculate_earnings(self):
        """Calculate insurable earnings from payslips with proper period allocation"""
        self.ensure_one()
        
        if not self.employee_id:
            raise UserError('Please select an employee first.')
        
        # Find all payslips for this employee in the last year
        year_ago = self.last_day_paid - timedelta(days=365) if self.last_day_paid else date.today() - timedelta(days=365)
        
        payslips = self.env['hr.payslip'].search([
            ('employee_id', '=', self.employee_id.id),
            ('state', '=', 'done'),
            ('date_from', '>=', year_ago),
            ('date_to', '<=', self.last_day_paid or date.today()),
        ], order='date_from asc', limit=53)  # Max 53 pay periods, order ascending for period allocation
        
        if not payslips:
            raise UserError('No payslips found for this employee in the specified period.')
        
        Payslip = self.env['hr.payslip']
        
        # Track earnings by period
        # Key: period index (0-based), Value: total insurable earnings for that period
        period_earnings = {}
        total_hours = 0
        total_earnings = 0
        
        # Process each payslip
        for idx, payslip in enumerate(payslips):
            # Get worked hours for this payslip
            worked_days = payslip.worked_days_line_ids
            hours = sum(wd.number_of_hours for wd in worked_days) if worked_days else 0
            total_hours += hours
            
            # Break down earnings by pay type
            period_earnings_for_which = 0  # Earnings for this period (work period)
            period_earnings_in_which = 0   # Earnings for next period (pay date)
            
            for line in payslip.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
                pay_type = Payslip._get_pay_type_from_code(code, category_code)
                is_reimbursement = Payslip._is_reimbursement(code, category_code)
                
                # Skip reimbursements - they are non-insurable
                if is_reimbursement:
                    continue
                
                # Skip union dues - they are deductions, not earnings
                if pay_type == 'union_dues':
                    continue
                
                # "For which period" allocation (work period)
                # Salary, Hourly, Overtime, Stat Holiday, Commission
                if pay_type in ['salary', 'hourly', 'overtime', 'stat_holiday', 'commission', 'other']:
                    period_earnings_for_which += amount
                
                # "In which period" allocation (pay date)
                # Bonus, Allowance, Vacation (paid as %)
                elif pay_type in ['bonus', 'allowance']:
                    period_earnings_in_which += amount
            
            # Allocate earnings to periods
            # "For which" earnings go to current period (idx)
            if idx not in period_earnings:
                period_earnings[idx] = 0
            period_earnings[idx] += period_earnings_for_which
            
            # "In which" earnings go to next period (idx + 1)
            # If it's the last payslip, allocate to current period
            next_period_idx = idx + 1 if idx < len(payslips) - 1 else idx
            if next_period_idx not in period_earnings:
                period_earnings[next_period_idx] = 0
            period_earnings[next_period_idx] += period_earnings_in_which
            
            # Add to total (both types are insurable)
            total_earnings += period_earnings_for_which + period_earnings_in_which
        
        # Clear existing pay period lines
        self.pay_period_earnings_ids.unlink()
        
        # Create pay period lines (ROE uses reverse order - most recent first)
        pay_period_data = []
        for period_idx in sorted(period_earnings.keys(), reverse=True):
            if period_idx < len(payslips):
                payslip = payslips[period_idx]
                earnings = period_earnings[period_idx]
                
                # ROE sequence numbers start from 1, most recent period is sequence 1
                sequence = len(payslips) - period_idx
                
                pay_period_data.append({
                    'roe_id': self.id,
                    'sequence': sequence,
                    'amount': earnings,
                    'payslip_id': payslip.id,
                })
        
        # Create new pay period lines
        if pay_period_data:
            self.env['hr.roe.pay.period'].create(pay_period_data)
        
        self.write({
            'total_insurable_hours': total_hours,
            'total_insurable_earnings': total_earnings,
        })
        
        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': 'Earnings Calculated',
                'message': f'Found {len(payslips)} pay periods. Total: ${total_earnings:,.2f}',
                'type': 'success',
            }
        }

    def action_generate_blk(self):
        """Generate BLK file for ROE Web submission"""
        self.ensure_one()
        
        blk_content = self._generate_blk_xml()
        
        # Encode to base64
        blk_data = base64.b64encode(blk_content.encode('utf-8'))
        
        # Generate filename
        employee_name = self.employee_id.name.replace(' ', '_')
        today = date.today().strftime('%Y-%m-%d')
        filename = f'ROEForm_{employee_name}_{today}.blk'
        
        self.write({
            'blk_file': blk_data,
            'blk_filename': filename,
            'state': 'ready',
        })
        
        # Post the file to chatter as attachment
        attachment = self.env['ir.attachment'].create({
            'name': filename,
            'type': 'binary',
            'datas': blk_data,
            'res_model': self._name,
            'res_id': self.id,
            'mimetype': 'application/xml',
        })
        
        # Post message with attachment
        self.message_post(
            body=f'BLK file generated: <strong>{filename}</strong><br/>Ready for submission to Service Canada ROE Web.',
            attachment_ids=[attachment.id],
            message_type='notification',
        )
        
        # Return download action
        return {
            'type': 'ir.actions.act_url',
            'url': f'/web/content/{attachment.id}?download=true',
            'target': 'self',
        }

    def _generate_blk_xml(self):
        """Generate the XML content for BLK file in CRA-compliant format"""
        self.ensure_one()
        
        # Format SIN (remove dashes/spaces)
        sin = (self.sin_number or '').replace('-', '').replace(' ', '')
        
        # Employee address
        emp = self.employee_id
        
        # Build pay period earnings XML with proper indentation
        pp_lines = []
        for pp in self.pay_period_earnings_ids:
            pp_lines.append(f'''         <PP nbr="{pp.sequence}">
            <AMT>{pp.amount:.2f}</AMT>
         </PP>''')
        pp_xml = '\n'.join(pp_lines)
        
        # Contact phone parts
        phone = (self.contact_phone or '').replace('-', '').replace(' ', '').replace('(', '').replace(')', '')
        area_code = phone[:3] if len(phone) >= 10 else ''
        phone_number = phone[3:10] if len(phone) >= 10 else phone
        
        # Get first name and last name
        name_parts = (emp.name or '').split() if emp.name else ['', '']
        first_name = name_parts[0] if name_parts else ''
        last_name = ' '.join(name_parts[1:]) if len(name_parts) > 1 else ''
        
        # Contact name parts
        contact_parts = (self.contact_name or '').split() if self.contact_name else ['', '']
        contact_first = contact_parts[0] if contact_parts else ''
        contact_last = ' '.join(contact_parts[1:]) if len(contact_parts) > 1 else ''
        
        # Build XML with proper CRA formatting
        xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<ROEHEADER FileVersion="W-2.0" ProductName="Fusion Payroll"
           SoftwareVendor="Nexa Systems Inc.">
    <ROE Issue="D" PrintingLanguage="{self.communication_language}">
      <B5>{self.cra_business_number or ''}</B5>
      <B6>{self.pay_period_type}</B6>
      <B8>{sin}</B8>
      <B9>
         <FN>{first_name}</FN>
         <LN>{last_name}</LN>
         <A1>{emp.home_street or ''}</A1>
         <A2>{emp.home_city or ''}</A2>
         <A3>{emp.home_province or 'ON'}, CA</A3>
         <PC>{(emp.home_postal_code or '').replace(' ', '')}</PC>
      </B9>
      <B10>{self.first_day_worked.strftime('%Y-%m-%d') if self.first_day_worked else ''}</B10>
      <B11>{self.last_day_paid.strftime('%Y-%m-%d') if self.last_day_paid else ''}</B11>
      <B12>{self.final_pay_period_end.strftime('%Y-%m-%d') if self.final_pay_period_end else ''}</B12>
      <B14>
         <CD>{'R' if self.expected_recall_date else 'U'}</CD>
      </B14>
      <B15A>{self.total_insurable_hours:.0f}</B15A>
      <B15C>
{pp_xml}
      </B15C>
      <B16>
         <CD>{self.reason_code}</CD>
         <FN>{contact_first}</FN>
         <LN>{contact_last}</LN>
         <AC>{area_code}</AC>
         <TEL>{phone_number}</TEL>
      </B16>
      <B20>{self.communication_language}</B20>
   </ROE>
</ROEHEADER>'''
        
        return xml

    def action_print_roe(self):
        """Print ROE as PDF"""
        self.ensure_one()
        return self.env.ref('fusion_payroll.action_report_roe').report_action(self)

    def action_mark_submitted(self):
        """Mark ROE as submitted to Service Canada"""
        self.ensure_one()
        self.write({
            'state': 'submitted',
            'submission_date': date.today(),
        })
        
        # Update employee ROE tracking
        self.employee_id.write({
            'roe_issued': True,
            'roe_issued_date': date.today(),
        })

    def action_archive(self):
        """Archive the ROE"""
        self.ensure_one()
        self.write({'state': 'archived'})


class HrROEPayPeriod(models.Model):
    _name = 'hr.roe.pay.period'
    _description = 'ROE Pay Period Earnings'
    _order = 'sequence'

    roe_id = fields.Many2one(
        'hr.roe',
        string='ROE',
        required=True,
        ondelete='cascade',
    )
    sequence = fields.Integer(
        string='Pay Period #',
        required=True,
    )
    amount = fields.Float(
        string='Insurable Earnings',
        digits=(10, 2),
    )
    payslip_id = fields.Many2one(
        'hr.payslip',
        string='Payslip',
    )
