# Fusion Accounting - Report Export Wizard
# Provides multi-format export capabilities for accounting reports

import base64
import json
import types
from urllib.parse import urlparse, parse_qs

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


class ReportExportWizard(models.TransientModel):
    """Transient wizard that enables batch export of accounting reports
    into various file formats, stored as downloadable attachments."""

    _name = 'account_reports.export.wizard'
    _description = "Fusion Accounting Report Export Wizard"

    export_format_ids = fields.Many2many(
        string="Target Formats",
        comodel_name='account_reports.export.wizard.format',
        relation="dms_acc_rep_export_wizard_format_rel",
    )
    report_id = fields.Many2one(
        string="Source Report",
        comodel_name='account.report',
        required=True,
    )
    doc_name = fields.Char(
        string="Output Filename",
        help="Base name applied to all generated output files.",
    )

    @api.model_create_multi
    def create(self, vals_list):
        """Override creation to auto-populate available export formats
        from the report's configured action buttons."""
        new_wizards = super().create(vals_list)

        for wiz in new_wizards:
            wiz.doc_name = wiz.report_id.name

            # Build format entries from the report's generation options
            generation_opts = self.env.context.get('account_report_generation_options', {})
            available_buttons = generation_opts.get('buttons', [])

            for btn_config in available_buttons:
                export_type = btn_config.get('file_export_type')
                if export_type:
                    self.env['account_reports.export.wizard.format'].create({
                        'name': export_type,
                        'fun_to_call': btn_config['action'],
                        'fun_param': btn_config.get('action_param'),
                        'export_wizard_id': wiz.id,
                    })

        return new_wizards

    def export_report(self):
        """Execute the export process: generate files in each selected format
        and return a window action displaying the resulting attachments."""
        self.ensure_one()

        saved_attachments = self.env['ir.attachment']
        for attachment_data in self._build_attachment_values():
            saved_attachments |= self.env['ir.attachment'].create(attachment_data)

        return {
            'type': 'ir.actions.act_window',
            'name': _('Generated Documents'),
            'view_mode': 'kanban,form',
            'res_model': 'ir.attachment',
            'domain': [('id', 'in', saved_attachments.ids)],
        }

    def _build_attachment_values(self):
        """Iterate over selected formats, invoke the corresponding report
        generator, and collect attachment value dictionaries."""
        self.ensure_one()

        attachment_vals_list = []
        current_options = self.env.context['account_report_generation_options']

        for fmt in self.export_format_ids:
            # Validate the callable name is a safe public method
            method_name = fmt.fun_to_call
            check_method_name(method_name)

            # Resolve whether the custom handler or base report owns the method
            target_report = self.report_id
            if target_report.custom_handler_model_id:
                handler_obj = self.env[target_report.custom_handler_model_name]
                if hasattr(handler_obj, method_name):
                    callable_fn = getattr(handler_obj, method_name)
                else:
                    callable_fn = getattr(target_report, method_name)
            else:
                callable_fn = getattr(target_report, method_name)

            extra_args = [fmt.fun_param] if fmt.fun_param else []
            action_result = callable_fn(current_options, *extra_args)

            attachment_vals_list.append(fmt.apply_export(action_result))

        return attachment_vals_list


class ReportExportFormatOption(models.TransientModel):
    """Represents a single selectable export format within the export wizard,
    linking a display label to the callable that produces the file."""

    _name = 'account_reports.export.wizard.format'
    _description = "Fusion Accounting Report Export Format"

    name = fields.Char(string="Format Label", required=True)
    fun_to_call = fields.Char(string="Generator Method", required=True)
    fun_param = fields.Char(string="Method Argument")
    export_wizard_id = fields.Many2one(
        string="Owning Wizard",
        comodel_name='account_reports.export.wizard',
        required=True,
        ondelete='cascade',
    )

    def apply_export(self, action_payload):
        """Convert a report action response into attachment-ready values.

        Handles two action types:
        - ir_actions_account_report_download: direct file generation
        - ir.actions.act_url: fetch file from a wizard model via URL params
        """
        self.ensure_one()

        if action_payload['type'] == 'ir_actions_account_report_download':
            opts_dict = json.loads(action_payload['data']['options'])

            # Resolve and invoke the file generator function
            generator_name = action_payload['data']['file_generator']
            check_method_name(generator_name)

            source_report = self.export_wizard_id.report_id
            if source_report.custom_handler_model_id:
                handler_env = self.env[source_report.custom_handler_model_name]
                if hasattr(handler_env, generator_name):
                    gen_callable = getattr(handler_env, generator_name)
                else:
                    gen_callable = getattr(source_report, generator_name)
            else:
                gen_callable = getattr(source_report, generator_name)

            output = gen_callable(opts_dict)

            # Encode raw bytes content to base64; handle generator objects
            raw_content = output['file_content']
            if isinstance(raw_content, bytes):
                encoded_data = base64.encodebytes(raw_content)
            elif isinstance(raw_content, types.GeneratorType):
                encoded_data = base64.encodebytes(b''.join(raw_content))
            else:
                encoded_data = raw_content

            output_name = (
                f"{self.export_wizard_id.doc_name or self.export_wizard_id.report_id.name}"
                f".{output['file_type']}"
            )
            content_mime = self.export_wizard_id.report_id.get_export_mime_type(
                output['file_type']
            )

        elif action_payload['type'] == 'ir.actions.act_url':
            # Extract file data from a URL-based wizard action
            url_parts = urlparse(action_payload['url'])
            params = parse_qs(url_parts.query)

            target_model = params['model'][0]
            record_id = int(params['id'][0])
            source_wizard = self.env[target_model].browse(record_id)

            output_name = source_wizard[params['filename_field'][0]]
            encoded_data = source_wizard[params['field'][0]]
            extension = output_name.split('.')[-1]
            content_mime = self.env['account.report'].get_export_mime_type(extension)
            opts_dict = {}

        else:
            raise UserError(
                _("The selected format cannot be exported as a document attachment.")
            )

        return self._prepare_attachment_dict(output_name, encoded_data, content_mime, opts_dict)

    def _prepare_attachment_dict(self, filename, binary_data, mime_type, options_log):
        """Build the dictionary of values for creating an ir.attachment record."""
        self.ensure_one()
        return {
            'name': filename,
            'company_id': self.env.company.id,
            'datas': binary_data,
            'mimetype': mime_type,
            'description': json.dumps(options_log),
        }
