# -*- coding: utf-8 -*-
"""
Payroll Cheque Management
=========================
Custom cheque printing for payroll with configurable layouts.
"""

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


class PayrollCheque(models.Model):
    """
    Payroll Cheque Record
    Tracks cheques issued for payroll payments.
    """
    _name = 'payroll.cheque'
    _description = 'Payroll Cheque'
    _order = 'cheque_date desc, cheque_number desc'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    name = fields.Char(
        string='Reference',
        compute='_compute_name',
        store=True,
    )
    cheque_number = fields.Char(
        string='Cheque Number',
        readonly=True,
        copy=False,
        tracking=True,
    )
    cheque_date = fields.Date(
        string='Cheque Date',
        required=True,
        default=fields.Date.context_today,
        tracking=True,
    )
    
    # Payee Information
    employee_id = fields.Many2one(
        'hr.employee',
        string='Employee',
        required=True,
        tracking=True,
    )
    payment_method_display = fields.Char(
        string='Payment Method',
        compute='_compute_payment_method_display',
    )
    payee_name = fields.Char(
        string='Payee Name',
        compute='_compute_payee_info',
        store=True,
    )
    payee_address = fields.Text(
        string='Payee Address',
        compute='_compute_payee_info',
        store=True,
    )
    
    # Amount
    amount = fields.Monetary(
        string='Amount',
        currency_field='currency_id',
        required=True,
        tracking=True,
    )
    amount_in_words = fields.Char(
        string='Amount in Words',
        compute='_compute_amount_in_words',
    )
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        default=lambda self: self.env.company.currency_id,
    )
    
    # Related Records
    payslip_id = fields.Many2one(
        'hr.payslip',
        string='Payslip',
        ondelete='set null',
    )
    payslip_run_id = fields.Many2one(
        'hr.payslip.run',
        string='Payslip Batch',
        ondelete='set null',
    )
    
    # Bank Account
    bank_account_id = fields.Many2one(
        'res.partner.bank',
        string='Bank Account',
        domain="[('company_id', '=', company_id)]",
    )
    company_id = fields.Many2one(
        'res.company',
        string='Company',
        required=True,
        default=lambda self: self.env.company,
    )
    
    # Pay Period Info
    pay_period_start = fields.Date(string='Pay Period Start')
    pay_period_end = fields.Date(string='Pay Period End')
    pay_period_display = fields.Char(
        string='Pay Period',
        compute='_compute_pay_period_display',
    )
    
    # Memo
    memo = fields.Text(string='Memo')
    
    # Status
    state = fields.Selection([
        ('draft', 'Draft'),
        ('printed', 'Printed'),
        ('voided', 'Voided'),
        ('cashed', 'Cashed'),
    ], string='Status', default='draft', tracking=True)
    
    printed_date = fields.Datetime(string='Printed Date', readonly=True)
    voided_date = fields.Datetime(string='Voided Date', readonly=True)
    void_reason = fields.Text(string='Void Reason')
    
    # ==================== COMPUTED FIELDS ====================
    
    @api.depends('cheque_number', 'employee_id')
    def _compute_name(self):
        for cheque in self:
            if cheque.cheque_number and cheque.employee_id:
                cheque.name = f"CHQ-{cheque.cheque_number} - {cheque.employee_id.name}"
            elif cheque.cheque_number:
                cheque.name = f"CHQ-{cheque.cheque_number}"
            else:
                cheque.name = _('New Cheque')
    
    @api.depends('employee_id')
    def _compute_payee_info(self):
        for cheque in self:
            if cheque.employee_id:
                emp = cheque.employee_id
                cheque.payee_name = emp.name
                
                # Build address from Fusion Payroll fields or fallback
                address_parts = []
                
                # Try Fusion Payroll home address fields
                if hasattr(emp, 'home_street') and emp.home_street:
                    address_parts.append(emp.home_street)
                if hasattr(emp, 'home_street2') and emp.home_street2:
                    address_parts.append(emp.home_street2)
                
                city_line = []
                if hasattr(emp, 'home_city') and emp.home_city:
                    city_line.append(emp.home_city)
                if hasattr(emp, 'home_province') and emp.home_province:
                    city_line.append(emp.home_province)
                if hasattr(emp, 'home_postal_code') and emp.home_postal_code:
                    city_line.append(emp.home_postal_code)
                
                if city_line:
                    address_parts.append(' '.join(city_line))
                
                # Fallback to private address
                if not address_parts and emp.private_street:
                    address_parts.append(emp.private_street)
                    if emp.private_city:
                        address_parts.append(f"{emp.private_city}, {emp.private_state_id.code or ''} {emp.private_zip or ''}")
                
                cheque.payee_address = '\n'.join(address_parts) if address_parts else ''
            else:
                cheque.payee_name = ''
                cheque.payee_address = ''
    
    @api.depends('amount', 'currency_id')
    def _compute_amount_in_words(self):
        for cheque in self:
            if cheque.amount:
                # Split into dollars and cents
                dollars = int(cheque.amount)
                cents = int(round((cheque.amount - dollars) * 100))
                
                # Convert to words
                dollars_words = num2words(dollars, lang='en').title()
                
                # Format: "One Thousand Three Hundred Fifty-Three and 47/100"
                cheque.amount_in_words = f"*****{dollars_words} and {cents:02d}/100"
            else:
                cheque.amount_in_words = ''
    
    @api.depends('pay_period_start', 'pay_period_end')
    def _compute_pay_period_display(self):
        for cheque in self:
            if cheque.pay_period_start and cheque.pay_period_end:
                cheque.pay_period_display = f"{cheque.pay_period_start.strftime('%m.%d.%Y')} - {cheque.pay_period_end.strftime('%m.%d.%Y')}"
            else:
                cheque.pay_period_display = ''
    
    @api.depends('employee_id', 'employee_id.payment_method')
    def _compute_payment_method_display(self):
        for cheque in self:
            if cheque.employee_id and hasattr(cheque.employee_id, 'payment_method'):
                method = cheque.employee_id.payment_method
                if method == 'cheque':
                    cheque.payment_method_display = 'Cheque'
                elif method == 'direct_deposit':
                    cheque.payment_method_display = 'Direct Deposit'
                else:
                    cheque.payment_method_display = method or 'N/A'
            else:
                cheque.payment_method_display = 'N/A'
    
    # ==================== ACTIONS ====================
    
    def action_assign_number(self):
        """Assign cheque number from sequence, checking for highest existing number."""
        for cheque in self:
            if not cheque.cheque_number:
                # Get the highest cheque number from payroll cheques
                max_payroll = self.env['payroll.cheque'].search([
                    ('cheque_number', '!=', False),
                    ('cheque_number', '!=', ''),
                    ('company_id', '=', cheque.company_id.id),
                ], order='cheque_number desc', limit=1)
                
                # Get the highest cheque number from account.payment (vendor payments)
                max_payment = 0
                if 'account.payment' in self.env:
                    payments = self.env['account.payment'].search([
                        ('check_number', '!=', False),
                        ('check_number', '!=', ''),
                        ('company_id', '=', cheque.company_id.id),
                    ])
                    for payment in payments:
                        try:
                            num = int(payment.check_number)
                            if num > max_payment:
                                max_payment = num
                        except (ValueError, TypeError):
                            pass
                
                # Get highest from payroll cheques
                max_payroll_num = 0
                if max_payroll and max_payroll.cheque_number:
                    try:
                        max_payroll_num = int(max_payroll.cheque_number)
                    except (ValueError, TypeError):
                        pass
                
                # Use the higher of the two, or sequence if both are 0
                next_num = max(max_payroll_num, max_payment) + 1
                
                if next_num > 1:
                    # Set sequence to next number
                    sequence = self.env['ir.sequence'].search([
                        ('code', '=', 'payroll.cheque'),
                        ('company_id', '=', cheque.company_id.id),
                    ], limit=1)
                    if sequence:
                        sequence.write({'number_next': next_num})
                    
                    cheque.cheque_number = str(next_num).zfill(6)
                else:
                    # Use sequence normally
                    cheque.cheque_number = self.env['ir.sequence'].next_by_code('payroll.cheque') or '/'
    
    def action_print_cheque(self):
        """Always open wizard to set/change cheque number before printing."""
        self.ensure_one()
        
        # Always open wizard to allow changing the cheque number
        return {
            'type': 'ir.actions.act_window',
            'name': _('Set Cheque Number'),
            'res_model': 'payroll.cheque.number.wizard',
            'view_mode': 'form',
            'target': 'new',
            'context': {
                'default_cheque_id': self.id,
            },
        }
    
    def action_void(self):
        """Void the cheque."""
        return {
            'type': 'ir.actions.act_window',
            'name': _('Void Cheque'),
            'res_model': 'payroll.cheque.void.wizard',
            'view_mode': 'form',
            'target': 'new',
            'context': {'default_cheque_id': self.id},
        }
    
    def action_mark_cashed(self):
        """Mark cheque as cashed."""
        self.write({'state': 'cashed'})
    
    def action_reset_to_draft(self):
        """Reset to draft (only for voided cheques)."""
        for cheque in self:
            if cheque.state == 'voided':
                cheque.write({
                    'state': 'draft',
                    'voided_date': False,
                    'void_reason': False,
                })
    
    # ==================== HELPER METHODS ====================
    
    def get_pay_stub_data(self):
        """Get pay stub data for the cheque report."""
        self.ensure_one()
        
        payslip = self.payslip_id
        if not payslip:
            return {}
        
        # Get payslip lines by category
        def get_line_amount(code):
            line = payslip.line_ids.filtered(lambda l: l.code == code)
            return line.total if line else 0
        
        # Calculate YTD values
        year_start = self.cheque_date.replace(month=1, day=1)
        ytd_payslips = self.env['hr.payslip'].search([
            ('employee_id', '=', self.employee_id.id),
            ('date_from', '>=', year_start),
            ('date_to', '<=', self.cheque_date),
            ('state', 'in', ['done', 'paid']),
        ])
        
        def get_ytd_amount(code):
            total = 0
            for slip in ytd_payslips:
                line = slip.line_ids.filtered(lambda l: l.code == code)
                total += line.total if line else 0
            return total
        
        # Get hourly rate
        hourly_rate = 0
        if hasattr(self.employee_id, 'hourly_rate'):
            hourly_rate = self.employee_id.hourly_rate or 0
        
        # Calculate hours from payslip inputs
        regular_hours = 0
        for input_line in payslip.input_line_ids:
            if 'hour' in (input_line.code or '').lower():
                regular_hours = input_line.amount
                break
        
        # Get regular pay - look for REGPAY first, then BASIC, then GROSS
        regular_pay_current = (get_line_amount('REGPAY') or get_line_amount('BASIC') or 
                               get_line_amount('GROSS') or payslip.basic_wage or 0)
        regular_pay_ytd = (get_ytd_amount('REGPAY') or get_ytd_amount('BASIC') or 
                           get_ytd_amount('GROSS') or 0)
        
        # Get vacation pay
        vacation_pay_current = get_line_amount('VAC') or get_line_amount('VACATION') or 0
        vacation_pay_ytd = get_ytd_amount('VAC') or get_ytd_amount('VACATION') or 0
        
        # Get stat holiday pay
        stat_pay_current = get_line_amount('STAT') or get_line_amount('STATHOLIDAY') or 0
        stat_pay_ytd = get_ytd_amount('STAT') or get_ytd_amount('STATHOLIDAY') or 0
        
        # Get taxes - these are negative in payslip, so use abs()
        # First try to get from payslip lines
        income_tax_current = abs(get_line_amount('FIT') or get_line_amount('INCOMETAX') or 0)
        ei_current = abs(get_line_amount('EI_EMP') or get_line_amount('EI') or 0)
        cpp_current = abs(get_line_amount('CPP_EMP') or get_line_amount('CPP') or 0)
        cpp2_current = abs(get_line_amount('CPP2_EMP') or get_line_amount('CPP2') or 0)
        
        # If individual line values are 0, calculate from payslip totals
        total_taxes_from_lines = income_tax_current + ei_current + cpp_current + cpp2_current
        if total_taxes_from_lines == 0 and payslip.basic_wage > 0 and payslip.net_wage > 0:
            # Calculate total taxes as difference between basic and net
            total_taxes_calculated = payslip.basic_wage - payslip.net_wage
            if total_taxes_calculated > 0:
                # Approximate breakdown based on typical Canadian tax rates
                # CPP ~5.95%, EI ~1.63%, Income Tax = remainder
                gross = payslip.basic_wage
                cpp_current = min(gross * 0.0595, 3867.50)  # 2025 CPP max
                ei_current = min(gross * 0.0163, 1049.12)   # 2025 EI max
                income_tax_current = max(0, total_taxes_calculated - cpp_current - ei_current)
                cpp2_current = 0  # Usually 0 unless over threshold
        
        income_tax_ytd = abs(get_ytd_amount('FIT') or get_ytd_amount('INCOMETAX') or 0)
        ei_ytd = abs(get_ytd_amount('EI_EMP') or get_ytd_amount('EI') or 0)
        cpp_ytd = abs(get_ytd_amount('CPP_EMP') or get_ytd_amount('CPP') or 0)
        cpp2_ytd = abs(get_ytd_amount('CPP2_EMP') or get_ytd_amount('CPP2') or 0)
        
        # Calculate totals
        total_taxes_current = income_tax_current + ei_current + cpp_current + cpp2_current
        total_taxes_ytd = income_tax_ytd + ei_ytd + cpp_ytd + cpp2_ytd
        
        total_pay_current = regular_pay_current + vacation_pay_current + stat_pay_current
        total_pay_ytd = regular_pay_ytd + vacation_pay_ytd + stat_pay_ytd
        
        # Get employer contributions
        employer_ei_current = abs(get_line_amount('EI_ER') or 0)
        employer_cpp_current = abs(get_line_amount('CPP_ER') or 0)
        employer_cpp2_current = abs(get_line_amount('CPP2_ER') or 0)
        
        # If no employer lines, calculate from employee amounts
        if employer_ei_current == 0 and ei_current > 0:
            employer_ei_current = ei_current * 1.4  # EI employer is 1.4x employee
        if employer_cpp_current == 0 and cpp_current > 0:
            employer_cpp_current = cpp_current  # CPP employer matches employee
        if employer_cpp2_current == 0 and cpp2_current > 0:
            employer_cpp2_current = cpp2_current  # CPP2 employer matches employee
        
        return {
            'pay': {
                'regular_pay': {
                    'hours': regular_hours,
                    'rate': hourly_rate,
                    'current': regular_pay_current,
                    'ytd': regular_pay_ytd,
                },
                'vacation_pay': {
                    'hours': '-',
                    'rate': '-',
                    'current': vacation_pay_current,
                    'ytd': vacation_pay_ytd,
                },
                'stat_holiday_pay': {
                    'hours': '-',
                    'rate': hourly_rate,
                    'current': stat_pay_current,
                    'ytd': stat_pay_ytd,
                },
            },
            'taxes': {
                'income_tax': {
                    'current': income_tax_current,
                    'ytd': income_tax_ytd,
                },
                'ei': {
                    'current': ei_current,
                    'ytd': ei_ytd,
                },
                'cpp': {
                    'current': cpp_current,
                    'ytd': cpp_ytd,
                },
                'cpp2': {
                    'current': cpp2_current,
                    'ytd': cpp2_ytd,
                },
            },
            'summary': {
                'total_pay': {
                    'current': total_pay_current,
                    'ytd': total_pay_ytd,
                },
                'taxes': {
                    'current': total_taxes_current,
                    'ytd': total_taxes_ytd,
                },
                'deductions': {
                    'current': 0,
                    'ytd': 0,
                },
                'net_pay': {
                    'current': self.amount,
                    'ytd': sum(s.net_wage for s in ytd_payslips) + self.amount,
                },
            },
            'benefits': {
                'vacation': {
                    'accrued': 0,
                    'used': 0,
                    'available': 0,
                },
            },
            'employer': {
                'ei': {
                    'current': employer_ei_current,
                },
                'cpp': {
                    'current': employer_cpp_current,
                },
                'cpp2': {
                    'current': employer_cpp2_current,
                },
            },
        }
    
    @api.model
    def create_from_payslip(self, payslip):
        """Create a cheque from a payslip."""
        # Check if employee payment method is cheque
        if hasattr(payslip.employee_id, 'payment_method') and payslip.employee_id.payment_method != 'cheque':
            return False
        
        # Check if cheque already exists for this payslip
        existing = self.search([('payslip_id', '=', payslip.id)], limit=1)
        if existing:
            return existing
        
        cheque = self.create({
            'employee_id': payslip.employee_id.id,
            'payslip_id': payslip.id,
            'payslip_run_id': payslip.payslip_run_id.id if payslip.payslip_run_id else False,
            'amount': payslip.net_wage,
            'cheque_date': payslip.date_to,
            'pay_period_start': payslip.date_from,
            'pay_period_end': payslip.date_to,
            'company_id': payslip.company_id.id,
        })
        
        # Link cheque back to payslip
        if cheque and hasattr(payslip, 'cheque_id'):
            payslip.cheque_id = cheque.id
        
        return cheque
    
    def action_mark_printed(self):
        """Mark cheque as printed and assign number if not assigned."""
        for cheque in self:
            if not cheque.cheque_number:
                cheque.action_assign_number()
            cheque.state = 'printed'


class PayrollChequeVoidWizard(models.TransientModel):
    """Wizard to void a cheque with reason."""
    _name = 'payroll.cheque.void.wizard'
    _description = 'Void Cheque Wizard'
    
    cheque_id = fields.Many2one('payroll.cheque', required=True)
    void_reason = fields.Text(string='Void Reason', required=True)
    
    def action_void(self):
        """Void the cheque."""
        self.cheque_id.write({
            'state': 'voided',
            'voided_date': fields.Datetime.now(),
            'void_reason': self.void_reason,
        })
        return {'type': 'ir.actions.act_window_close'}


class PayrollChequeLayout(models.Model):
    """
    Cheque Layout Configuration
    Allows customizing field positions on the cheque.
    """
    _name = 'payroll.cheque.layout'
    _description = 'Cheque Layout'
    
    name = fields.Char(string='Layout Name', required=True)
    active = fields.Boolean(default=True)
    company_id = fields.Many2one(
        'res.company',
        default=lambda self: self.env.company,
    )
    
    # Page Settings
    page_width = fields.Float(string='Page Width (inches)', default=8.5)
    page_height = fields.Float(string='Page Height (inches)', default=11)
    
    # Cheque Section (Top)
    cheque_height = fields.Float(string='Cheque Height (inches)', default=3.5)
    
    # Date Position
    date_x = fields.Float(string='Date X Position', default=6.5)
    date_y = fields.Float(string='Date Y Position', default=0.5)
    date_format = fields.Selection([
        ('mmddyyyy', 'MMDDYYYY'),
        ('mm/dd/yyyy', 'MM/DD/YYYY'),
        ('yyyy-mm-dd', 'YYYY-MM-DD'),
    ], string='Date Format', default='mmddyyyy')
    
    # Amount Position
    amount_x = fields.Float(string='Amount X Position', default=6.5)
    amount_y = fields.Float(string='Amount Y Position', default=1.5)
    
    # Amount in Words Position
    amount_words_x = fields.Float(string='Amount Words X', default=0.5)
    amount_words_y = fields.Float(string='Amount Words Y', default=1.0)
    
    # Payee Position
    payee_x = fields.Float(string='Payee X Position', default=0.5)
    payee_y = fields.Float(string='Payee Y Position', default=1.5)
    
    # Memo Position
    memo_x = fields.Float(string='Memo X Position', default=0.5)
    memo_y = fields.Float(string='Memo Y Position', default=2.5)
    
    # Pay Period Position
    pay_period_x = fields.Float(string='Pay Period X', default=0.5)
    pay_period_y = fields.Float(string='Pay Period Y', default=2.0)
    
    # Stub Settings
    stub_height = fields.Float(string='Stub Height (inches)', default=3.75)
    show_employer_copy = fields.Boolean(string='Show Employer Copy', default=True)
    
    @api.model
    def get_default_layout(self):
        """Get or create the default layout."""
        layout = self.search([
            ('company_id', '=', self.env.company.id),
        ], limit=1)
        
        if not layout:
            layout = self.create({
                'name': 'Default Cheque Layout',
                'company_id': self.env.company.id,
            })
        
        return layout
