# -*- coding: utf-8 -*-
"""
Payroll Report Framework
========================
Base abstract model for all payroll reports, modeled after Odoo's account_reports.
Provides filter options, data fetching, and export functionality.
"""

import io
import json
from datetime import date, datetime
from dateutil.relativedelta import relativedelta

from odoo import api, fields, models, _
from odoo.tools import format_date, float_round
from odoo.tools.misc import formatLang


class PayrollReport(models.AbstractModel):
    """
    Abstract base class for all payroll reports.
    Provides common filter options, column definitions, and export methods.
    """
    _name = 'payroll.report'
    _description = 'Payroll Report Base'

    # =========================================================================
    # REPORT CONFIGURATION (Override in concrete reports)
    # =========================================================================
    
    # Filter options - set to True to enable
    filter_date_range = True
    filter_employee = True
    filter_department = False
    filter_pay_period = False
    
    # Report metadata
    report_name = 'Payroll Report'
    report_code = 'payroll_report'

    # Date filter presets matching QuickBooks
    DATE_FILTER_OPTIONS = [
        ('last_pay_date', 'Last pay date'),
        ('this_month', 'This month'),
        ('this_quarter', 'This quarter'),
        ('this_year', 'This year'),
        ('last_month', 'Last month'),
        ('last_quarter', 'Last quarter'),
        ('last_year', 'Last year'),
        ('first_quarter', 'First quarter'),
        ('custom', 'Custom'),
    ]

    # =========================================================================
    # OPTIONS HANDLING
    # =========================================================================

    def _get_default_options(self):
        """Return default options for the report."""
        today = date.today()
        
        options = {
            'report_name': self.report_name,
            'report_code': self.report_code,
            'date': {
                'filter': 'this_year',
                'date_from': today.replace(month=1, day=1).strftime('%Y-%m-%d'),
                'date_to': today.strftime('%Y-%m-%d'),
            },
            'columns': self._get_columns(),
            'currency_id': self.env.company.currency_id.id,
        }
        
        if self.filter_employee:
            options['employee_ids'] = []
            options['all_employees'] = True
            
        if self.filter_department:
            options['department_ids'] = []
            options['all_departments'] = True
            
        return options

    def _get_options(self, previous_options=None):
        """
        Build report options, merging with previous options if provided.
        """
        options = self._get_default_options()
        
        if previous_options:
            # Merge date options
            if 'date' in previous_options:
                options['date'].update(previous_options['date'])
                
            # Merge employee filter
            if 'employee_ids' in previous_options:
                options['employee_ids'] = previous_options['employee_ids']
                options['all_employees'] = not previous_options['employee_ids']
                
            # Merge department filter
            if 'department_ids' in previous_options:
                options['department_ids'] = previous_options['department_ids']
                options['all_departments'] = not previous_options['department_ids']
        
        # Apply date filter preset
        self._apply_date_filter(options)
        
        return options

    def _apply_date_filter(self, options):
        """Calculate date_from and date_to based on filter preset."""
        today = date.today()
        date_filter = options.get('date', {}).get('filter', 'this_year')
        
        if date_filter == 'custom':
            # Keep existing dates
            return
            
        date_from = today
        date_to = today
        
        if date_filter == 'last_pay_date':
            # Find the last payslip date
            last_payslip = self.env['hr.payslip'].search([
                ('state', 'in', ['done', 'paid']),
                ('company_id', '=', self.env.company.id),
            ], order='date_to desc', limit=1)
            if last_payslip:
                date_from = last_payslip.date_from
                date_to = last_payslip.date_to
            else:
                date_from = today
                date_to = today
                
        elif date_filter == 'this_month':
            date_from = today.replace(day=1)
            date_to = (today.replace(day=1) + relativedelta(months=1)) - relativedelta(days=1)
            
        elif date_filter == 'this_quarter':
            quarter_month = ((today.month - 1) // 3) * 3 + 1
            date_from = today.replace(month=quarter_month, day=1)
            date_to = (date_from + relativedelta(months=3)) - relativedelta(days=1)
            
        elif date_filter == 'this_year':
            date_from = today.replace(month=1, day=1)
            date_to = today.replace(month=12, day=31)
            
        elif date_filter == 'last_month':
            last_month = today - relativedelta(months=1)
            date_from = last_month.replace(day=1)
            date_to = today.replace(day=1) - relativedelta(days=1)
            
        elif date_filter == 'last_quarter':
            quarter_month = ((today.month - 1) // 3) * 3 + 1
            last_quarter_start = today.replace(month=quarter_month, day=1) - relativedelta(months=3)
            date_from = last_quarter_start
            date_to = (last_quarter_start + relativedelta(months=3)) - relativedelta(days=1)
            
        elif date_filter == 'last_year':
            date_from = today.replace(year=today.year - 1, month=1, day=1)
            date_to = today.replace(year=today.year - 1, month=12, day=31)
            
        elif date_filter == 'first_quarter':
            date_from = today.replace(month=1, day=1)
            date_to = today.replace(month=3, day=31)
        
        options['date']['date_from'] = date_from.strftime('%Y-%m-%d')
        options['date']['date_to'] = date_to.strftime('%Y-%m-%d')

    # =========================================================================
    # COLUMN DEFINITIONS (Override in concrete reports)
    # =========================================================================

    def _get_columns(self):
        """
        Return column definitions for the report.
        Override in concrete reports.
        
        Returns:
            list: List of column dictionaries with:
                - name: Display name
                - field: Data field name
                - type: 'char', 'date', 'monetary', 'float', 'integer'
                - sortable: bool
                - width: optional width class
        """
        return [
            {'name': _('Name'), 'field': 'name', 'type': 'char', 'sortable': True},
        ]

    # =========================================================================
    # DATA FETCHING (Override in concrete reports)
    # =========================================================================

    def _get_domain(self, options):
        """
        Build search domain from options.
        Override in concrete reports for specific filtering.
        """
        domain = [('company_id', '=', self.env.company.id)]
        
        # Date filter
        date_from = options.get('date', {}).get('date_from')
        date_to = options.get('date', {}).get('date_to')
        if date_from:
            domain.append(('date_from', '>=', date_from))
        if date_to:
            domain.append(('date_to', '<=', date_to))
            
        # Employee filter
        if not options.get('all_employees') and options.get('employee_ids'):
            domain.append(('employee_id', 'in', options['employee_ids']))
            
        # Department filter
        if not options.get('all_departments') and options.get('department_ids'):
            domain.append(('employee_id.department_id', 'in', options['department_ids']))
            
        return domain

    def _get_lines(self, options):
        """
        Fetch and return report lines.
        Override in concrete reports.
        
        Returns:
            list: List of line dictionaries with:
                - id: unique line id
                - name: display name
                - columns: list of column values
                - level: hierarchy level (0, 1, 2...)
                - unfoldable: bool for drill-down
                - unfolded: bool current state
                - class: CSS classes
        """
        return []

    def _get_report_data(self, options):
        """
        Get complete report data for frontend.
        """
        return {
            'options': options,
            'columns': self._get_columns(),
            'lines': self._get_lines(options),
            'date_filter_options': self.DATE_FILTER_OPTIONS,
        }

    # =========================================================================
    # TOTALS AND AGGREGATIONS
    # =========================================================================

    def _get_total_line(self, lines, options):
        """
        Calculate totals from lines.
        Override for custom total calculations.
        """
        if not lines:
            return {}
        
        columns = self._get_columns()
        total_values = {}
        
        for col in columns:
            if col.get('type') in ('monetary', 'float', 'integer'):
                field = col['field']
                try:
                    total_values[field] = sum(
                        float(line.get('values', {}).get(field, 0) or 0)
                        for line in lines
                        if line.get('level', 0) == 0  # Only sum top-level lines
                    )
                except (ValueError, TypeError):
                    total_values[field] = 0
        
        return {
            'id': 'total',
            'name': _('Total'),
            'values': total_values,
            'level': -1,  # Special level for total
            'class': 'o_payroll_report_total fw-bold',
        }

    # =========================================================================
    # SETTINGS HELPERS
    # =========================================================================

    def _get_payroll_settings(self):
        """Get payroll settings for current company."""
        return self.env['payroll.config.settings'].get_settings()

    def _get_company_legal_info(self):
        """Get company legal name and address from settings."""
        settings = self._get_payroll_settings()
        return {
            'legal_name': settings.company_legal_name or self.env.company.name,
            'legal_street': settings.company_legal_street or self.env.company.street or '',
            'legal_street2': settings.company_legal_street2 or self.env.company.street2 or '',
            'legal_city': settings.company_legal_city or self.env.company.city or '',
            'legal_state': settings.company_legal_state_id.name if settings.company_legal_state_id else (self.env.company.state_id.name if self.env.company.state_id else ''),
            'legal_zip': settings.company_legal_zip or self.env.company.zip or '',
            'legal_country': settings.company_legal_country_id.name if settings.company_legal_country_id else (self.env.company.country_id.name if self.env.company.country_id else ''),
        }

    def _get_payroll_contact_info(self):
        """Get payroll contact information from settings."""
        settings = self._get_payroll_settings()
        return {
            'name': settings.get_payroll_contact_name(),
            'phone': settings.payroll_contact_phone or '',
            'email': settings.payroll_contact_email or '',
        }

    # =========================================================================
    # FORMATTING HELPERS
    # =========================================================================

    def _format_value(self, value, column_type, options):
        """Format value based on column type."""
        if value is None:
            return ''
            
        currency = self.env['res.currency'].browse(options.get('currency_id'))
        
        if column_type == 'monetary':
            return formatLang(self.env, value, currency_obj=currency)
        elif column_type == 'float':
            return formatLang(self.env, value, digits=2)
        elif column_type == 'integer':
            return str(int(value))
        elif column_type == 'date':
            if isinstance(value, str):
                value = fields.Date.from_string(value)
            return format_date(self.env, value)
        elif column_type == 'percentage':
            return f"{float_round(value * 100, 2)}%"
        else:
            return str(value) if value else ''

    # =========================================================================
    # EXPORT METHODS
    # =========================================================================

    def get_xlsx(self, options):
        """
        Generate Excel export of the report.
        Returns binary data.
        """
        try:
            import xlsxwriter
        except ImportError:
            raise ImportError("xlsxwriter library is required for Excel export")
        
        output = io.BytesIO()
        workbook = xlsxwriter.Workbook(output, {'in_memory': True})
        sheet = workbook.add_worksheet(self.report_name[:31])  # Max 31 chars
        
        # Styles
        header_style = workbook.add_format({
            'bold': True,
            'bg_color': '#4a86e8',
            'font_color': 'white',
            'border': 1,
        })
        money_style = workbook.add_format({'num_format': '$#,##0.00'})
        date_style = workbook.add_format({'num_format': 'yyyy-mm-dd'})
        total_style = workbook.add_format({'bold': True, 'top': 2})
        
        # Get data
        columns = self._get_columns()
        lines = self._get_lines(options)
        
        # Add company info from settings
        company_info = self._get_company_legal_info()
        contact_info = self._get_payroll_contact_info()
        
        # Write report header with company info
        sheet.write(0, 0, company_info['legal_name'] or self.env.company.name, header_style)
        if company_info['legal_street']:
            sheet.write(1, 0, company_info['legal_street'])
        if company_info['legal_city']:
            addr_line = f"{company_info['legal_city']}, {company_info['legal_state']} {company_info['legal_zip']}"
            sheet.write(2, 0, addr_line)
        if contact_info['name']:
            sheet.write(3, 0, f"Contact: {contact_info['name']}")
        
        # Write headers (starting at row 5)
        header_row = 5
        for col_idx, col in enumerate(columns):
            sheet.write(header_row, col_idx, col['name'], header_style)
            sheet.set_column(col_idx, col_idx, 15)
        
        # Write data (starting after header row)
        row = header_row + 1
        for line in lines:
            values = line.get('values', {})
            style = total_style if line.get('level', 0) < 0 else None
            
            for col_idx, col in enumerate(columns):
                value = values.get(col['field'], '')
                cell_style = style
                
                if col['type'] == 'monetary':
                    cell_style = money_style
                elif col['type'] == 'date':
                    cell_style = date_style
                    
                if cell_style:
                    sheet.write(row, col_idx, value, cell_style)
                else:
                    sheet.write(row, col_idx, value)
            row += 1
        
        workbook.close()
        output.seek(0)
        return output.read()

    def get_pdf(self, options):
        """
        Generate PDF export of the report.
        Returns binary data.
        """
        report_data = self._get_report_data(options)
        
        # Get settings data
        company_info = self._get_company_legal_info()
        contact_info = self._get_payroll_contact_info()
        
        # Render QWeb template
        html = self.env['ir.qweb']._render(
            'fusion_payroll.payroll_report_pdf_template',
            {
                'report': self,
                'options': options,
                'columns': report_data['columns'],
                'lines': report_data['lines'],
                'company': self.env.company,
                'company_info': company_info,
                'contact_info': contact_info,
                'format_value': self._format_value,
            }
        )
        
        # Convert to PDF using wkhtmltopdf
        pdf = self.env['ir.actions.report']._run_wkhtmltopdf(
            [html],
            landscape=True,
            specific_paperformat_args={'data-report-margin-top': 10}
        )
        
        return pdf

    # =========================================================================
    # ACTION METHODS
    # =========================================================================

    def action_open_report(self):
        """Open the report in client action."""
        return {
            'type': 'ir.actions.client',
            'tag': 'fusion_payroll.payroll_report_action',
            'name': self.report_name,
            'context': {
                'report_model': self._name,
            },
        }
