# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)

import base64
import json
import logging
from datetime import datetime, timedelta

from odoo import api, fields, models, _
from odoo.exceptions import UserError
from markupsafe import Markup

_logger = logging.getLogger(__name__)


class FusionFax(models.Model):
    _name = 'fusion.fax'
    _description = 'Fax Record'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _order = 'create_date desc'
    _rec_name = 'name'

    name = fields.Char(
        string='Reference',
        required=True,
        copy=False,
        readonly=True,
        default=lambda self: _('New'),
    )
    direction = fields.Selection([
        ('outbound', 'Outbound'),
        ('inbound', 'Inbound'),
    ], string='Direction', default='outbound', required=True, tracking=True)

    partner_id = fields.Many2one(
        'res.partner',
        string='Recipient',
        tracking=True,
    )
    fax_number = fields.Char(
        string='Fax Number',
        required=True,
        tracking=True,
    )
    state = fields.Selection([
        ('draft', 'Draft'),
        ('sending', 'Sending'),
        ('sent', 'Sent'),
        ('failed', 'Failed'),
        ('received', 'Received'),
    ], string='Status', default='draft', required=True, tracking=True)

    # Inbound fax fields
    sender_number = fields.Char(
        string='Sender Number',
        readonly=True,
    )
    received_date = fields.Datetime(
        string='Received Date',
        readonly=True,
    )
    rc_read_status = fields.Char(
        string='Read Status',
        readonly=True,
    )

    # Computed display fields for list views (work regardless of direction)
    display_date = fields.Datetime(
        string='Date',
        compute='_compute_display_fields',
        store=True,
    )
    display_number = fields.Char(
        string='Fax Number',
        compute='_compute_display_fields',
        store=True,
    )

    cover_page_text = fields.Text(string='Cover Page Text')
    document_ids = fields.One2many(
        'fusion.fax.document',
        'fax_id',
        string='Documents',
    )
    document_count = fields.Integer(
        compute='_compute_document_count',
    )
    # Keep for backwards compat with existing records
    attachment_ids = fields.Many2many(
        'ir.attachment',
        'fusion_fax_attachment_rel',
        'fax_id',
        'attachment_id',
        string='Attachments (Legacy)',
    )
    ringcentral_message_id = fields.Char(
        string='RingCentral Message ID',
        readonly=True,
        copy=False,
    )
    sent_date = fields.Datetime(
        string='Sent Date',
        readonly=True,
        copy=False,
    )
    sent_by_id = fields.Many2one(
        'res.users',
        string='Sent By',
        readonly=True,
        default=lambda self: self.env.user,
    )
    page_count = fields.Integer(
        string='Pages',
        readonly=True,
        copy=False,
    )
    error_message = fields.Text(
        string='Error Message',
        readonly=True,
        copy=False,
    )

    # Links to source documents
    sale_order_id = fields.Many2one(
        'sale.order',
        string='Sale Order',
        ondelete='set null',
        tracking=True,
    )
    account_move_id = fields.Many2one(
        'account.move',
        string='Invoice',
        ondelete='set null',
        tracking=True,
    )

    def write(self, vals):
        """Post chatter message when a sale order or invoice is linked."""
        old_so_ids = {rec.id: rec.sale_order_id.id for rec in self}
        result = super().write(vals)

        if 'sale_order_id' in vals:
            for rec in self:
                new_so = rec.sale_order_id
                old_so_id = old_so_ids.get(rec.id)
                if new_so and new_so.id != old_so_id:
                    rec._post_link_chatter_message(new_so)
                    # Also set the partner from the SO if not already matched
                    if not rec.partner_id and new_so.partner_id:
                        rec.partner_id = new_so.partner_id

        return result

    def _post_link_chatter_message(self, sale_order):
        """Post a message on the sale order when a fax is linked to it."""
        self.ensure_one()
        direction_label = 'Received' if self.direction == 'inbound' else 'Sent'
        date_str = ''
        if self.direction == 'inbound' and self.received_date:
            date_str = self.received_date.strftime('%b %d, %Y %H:%M')
        elif self.sent_date:
            date_str = self.sent_date.strftime('%b %d, %Y %H:%M')

        number = self.sender_number or self.fax_number or ''
        body = Markup(
            '<p><strong>Fax Linked</strong></p>'
            '<p>%s fax <a href="/odoo/faxes/%s"><b>%s</b></a> has been linked to this order.</p>'
            '<ul>'
            '<li>Fax Number: %s</li>'
            '<li>Date: %s</li>'
            '<li>Pages: %s</li>'
            '</ul>'
        ) % (direction_label, self.id, self.name, number,
             date_str or '-', self.page_count or '-')

        # Attach the fax documents to the chatter message
        attachment_ids = self.document_ids.mapped('attachment_id').ids
        sale_order.message_post(
            body=body,
            message_type='notification',
            attachment_ids=attachment_ids,
        )

    @api.depends('document_ids')
    def _compute_document_count(self):
        for rec in self:
            rec.document_count = len(rec.document_ids)

    @api.depends('direction', 'sent_date', 'received_date', 'fax_number', 'sender_number')
    def _compute_display_fields(self):
        for rec in self:
            if rec.direction == 'inbound':
                rec.display_date = rec.received_date
                rec.display_number = rec.sender_number or rec.fax_number
            else:
                rec.display_date = rec.sent_date
                rec.display_number = rec.fax_number

    @api.model_create_multi
    def create(self, vals_list):
        for vals in vals_list:
            if vals.get('name', _('New')) == _('New'):
                vals['name'] = self.env['ir.sequence'].next_by_code('fusion.fax') or _('New')
        return super().create(vals_list)

    # ------------------------------------------------------------------
    # RingCentral helpers
    # ------------------------------------------------------------------

    def _get_rc_config(self):
        """Return the active rc.config record or raise."""
        try:
            config = self.env['rc.config']._get_active_config()
        except Exception:
            config = False
        if not config:
            raise UserError(_(
                'RingCentral is not connected. '
                'Go to Settings > Fusion RingCentral and connect via OAuth.'
            ))
        return config

    def _get_rc_sdk(self):
        """Initialize and authenticate the RingCentral SDK. Returns (sdk, platform) tuple.

        Tries JWT credentials first (Fusion Faxes settings), then falls back
        to the rc.config OAuth credentials + SDK JWT if available.
        """
        ICP = self.env['ir.config_parameter'].sudo()
        enabled = ICP.get_param('fusion_faxes.ringcentral_enabled', 'False')
        if enabled not in ('True', 'true', '1'):
            raise UserError(_('RingCentral faxing is not enabled. Go to Settings > Fusion Faxes to enable it.'))

        client_id = ICP.get_param('fusion_faxes.ringcentral_client_id', '')
        client_secret = ICP.get_param('fusion_faxes.ringcentral_client_secret', '')
        server_url = ICP.get_param('fusion_faxes.ringcentral_server_url', 'https://platform.ringcentral.com')
        jwt_token = ICP.get_param('fusion_faxes.ringcentral_jwt_token', '')

        if not all([client_id, client_secret, jwt_token]):
            raise UserError(_(
                'RingCentral JWT credentials are not configured. '
                'Go to Settings > Fusion Faxes and enter Client ID, Client Secret, and JWT Token. '
                'JWT is required for outbound fax sending.'
            ))

        try:
            from ringcentral import SDK
        except ImportError:
            raise UserError(_(
                'The ringcentral Python package is not installed. '
                'Run: pip install ringcentral'
            ))

        sdk = SDK(client_id, client_secret, server_url)
        platform = sdk.platform()
        platform.login(jwt=jwt_token)
        return sdk, platform

    def _get_ordered_attachments(self):
        """Return attachments in the correct order: document_ids by sequence, or legacy attachment_ids."""
        self.ensure_one()
        if self.document_ids:
            return self.document_ids.sorted('sequence').mapped('attachment_id')
        return self.attachment_ids

    def _send_fax(self):
        """Send this fax record via RingCentral API.

        Tries JWT/SDK first (if configured), then falls back to
        rc.config OAuth with raw multipart POST.
        """
        self.ensure_one()

        attachments = self._get_ordered_attachments()
        if not attachments:
            raise UserError(_('Please attach at least one document to send.'))

        self.write({'state': 'sending', 'error_message': False})

        ICP = self.env['ir.config_parameter'].sudo()
        jwt_token = ICP.get_param('fusion_faxes.ringcentral_jwt_token', '')
        if jwt_token:
            self._send_fax_sdk(attachments)
        else:
            self._send_fax_oauth(attachments)

    def _send_fax_sdk(self, attachments):
        """Send fax using the RingCentral Python SDK (JWT auth)."""
        try:
            sdk, platform = self._get_rc_sdk()
            builder = sdk.create_multipart_builder()
            body = {
                'to': [{'phoneNumber': self.fax_number}],
                'faxResolution': 'High',
            }
            if self.cover_page_text:
                body['coverPageText'] = self.cover_page_text
            builder.set_body(body)

            for attachment in attachments:
                file_content = base64.b64decode(attachment.datas)
                builder.add((attachment.name, file_content))

            request = builder.request('/restapi/v1.0/account/~/extension/~/fax')
            response = platform.send_request(request)
            result = response.json()

            message_id = ''
            page_count = 0
            if isinstance(result, dict):
                message_id = str(result.get('id', ''))
                page_count = result.get('pageCount', 0)
            else:
                message_id = str(getattr(result, 'id', ''))
                page_count = getattr(result, 'pageCount', 0)

            self._finalize_send(message_id, page_count)
        except UserError:
            raise
        except Exception as e:
            self._handle_send_error(e)

    def _send_fax_oauth(self, attachments):
        """Send fax using rc.config OAuth with multipart POST."""
        import requests as _requests

        try:
            rc_config = self._get_rc_config()
            headers = rc_config._get_headers()
            del headers['Content-Type']

            body = {
                'to': [{'phoneNumber': self.fax_number}],
                'faxResolution': 'High',
            }
            if self.cover_page_text:
                body['coverPageText'] = self.cover_page_text

            files = [
                ('json', (None, json.dumps(body), 'application/json')),
            ]
            for attachment in attachments:
                file_content = base64.b64decode(attachment.datas)
                mime = attachment.mimetype or 'application/pdf'
                files.append(('attachment', (attachment.name, file_content, mime)))

            url = f'{rc_config.server_url}/restapi/v1.0/account/~/extension/~/fax'
            resp = _requests.post(
                url,
                headers=headers,
                files=files,
                timeout=60,
                verify=rc_config.ssl_verify,
                proxies=rc_config._get_proxies(),
            )
            resp.raise_for_status()
            result = resp.json()

            message_id = str(result.get('id', ''))
            page_count = result.get('pageCount', 0)
            self._finalize_send(message_id, page_count)
        except UserError:
            raise
        except Exception as e:
            self._handle_send_error(e)

    def _finalize_send(self, message_id, page_count):
        self.write({
            'state': 'sent',
            'ringcentral_message_id': message_id,
            'sent_date': fields.Datetime.now(),
            'sent_by_id': self.env.user.id,
            'page_count': page_count,
        })
        self._post_fax_chatter_message(success=True)
        _logger.info("Fax %s sent successfully. RC Message ID: %s", self.name, message_id)

    def _handle_send_error(self, e):
        error_msg = str(e)
        self.write({
            'state': 'failed',
            'error_message': error_msg,
        })
        self._post_fax_chatter_message(success=False)
        _logger.exception("Fax %s failed to send", self.name)
        raise UserError(_('Fax sending failed: %s') % error_msg)

    def _post_fax_chatter_message(self, success=True):
        """Post a chatter message on the linked sale order or invoice."""
        self.ensure_one()
        if success:
            body = Markup(
                '<p><strong>Fax Sent</strong></p>'
                '<p>Fax <b>%s</b> sent successfully to <b>%s</b> (%s).</p>'
                '<p>Pages: %s | RingCentral ID: %s</p>'
            ) % (self.name, self.partner_id.name, self.fax_number,
                 self.page_count or '-', self.ringcentral_message_id or '-')
        else:
            body = Markup(
                '<p><strong>Fax Failed</strong></p>'
                '<p>Fax <b>%s</b> to <b>%s</b> (%s) failed.</p>'
                '<p>Error: %s</p>'
            ) % (self.name, self.partner_id.name, self.fax_number,
                 self.error_message or 'Unknown error')

        if self.sale_order_id:
            self.sale_order_id.message_post(body=body, message_type='notification')
        if self.account_move_id:
            self.account_move_id.message_post(body=body, message_type='notification')

    # ------------------------------------------------------------------
    # Actions
    # ------------------------------------------------------------------

    def action_send(self):
        """Button action to send this fax."""
        self.ensure_one()
        self._send_fax()

    def action_retry(self):
        """Retry a failed fax."""
        self.ensure_one()
        if self.state != 'failed':
            raise UserError(_('Only failed faxes can be retried.'))
        self._send_fax()

    def action_resend(self):
        """Resend a previously sent fax with all the same attachments."""
        self.ensure_one()
        if self.state != 'sent':
            raise UserError(_('Only sent faxes can be resent.'))
        self._send_fax()

    def action_open_sale_order(self):
        """Open the linked sale order."""
        self.ensure_one()
        if not self.sale_order_id:
            return
        return {
            'type': 'ir.actions.act_window',
            'name': self.sale_order_id.name,
            'res_model': 'sale.order',
            'res_id': self.sale_order_id.id,
            'view_mode': 'form',
        }

    def action_reset_to_draft(self):
        """Reset a failed fax back to draft."""
        self.ensure_one()
        if self.state not in ('failed',):
            raise UserError(_('Only failed faxes can be reset to draft.'))
        self.write({
            'state': 'draft',
            'error_message': False,
        })

    # ------------------------------------------------------------------
    # Incoming fax polling (uses rc.config OAuth)
    # ------------------------------------------------------------------

    @api.model
    def _cron_fetch_incoming_faxes(self):
        """Poll RingCentral for inbound faxes via rc.config OAuth."""
        ICP = self.env['ir.config_parameter'].sudo()
        enabled = ICP.get_param('fusion_faxes.ringcentral_enabled', 'False')
        if enabled not in ('True', 'true', '1'):
            return

        try:
            rc_config = self.env['rc.config']._get_active_config()
        except Exception:
            rc_config = False
        if not rc_config:
            _logger.debug("Fusion Faxes: No active RingCentral config, skipping inbound poll.")
            return

        last_poll = ICP.get_param('fusion_faxes.last_inbound_poll', '')
        if not last_poll:
            date_from = (datetime.utcnow() - timedelta(days=30)).strftime('%Y-%m-%dT%H:%M:%S.000Z')
        else:
            date_from = last_poll

        try:
            self._fetch_faxes_from_rc(rc_config, date_from)
        except Exception:
            _logger.exception("Fusion Faxes: Error fetching inbound faxes.")

    @api.model
    def _run_historical_fax_import(self):
        """Background job: import up to 12 months of inbound faxes in monthly chunks."""
        rc_config = self.env['rc.config']._get_active_config()
        if not rc_config:
            _logger.warning("Fax Historical Import: No connected RC config.")
            return

        ICP = self.env['ir.config_parameter'].sudo()
        now = datetime.utcnow()
        total_imported = 0

        for months_back in range(12, 0, -1):
            chunk_start = now - timedelta(days=months_back * 30)
            chunk_key = f'fusion_rc.fax_import_done_{chunk_start.strftime("%Y%m")}'
            if ICP.get_param(chunk_key, ''):
                continue

            date_from = chunk_start.strftime('%Y-%m-%dT%H:%M:%S.000Z')
            date_to = (now - timedelta(days=(months_back - 1) * 30)).strftime('%Y-%m-%dT%H:%M:%S.000Z')

            _logger.info("Fax Import: chunk %s to %s ...", date_from[:10], date_to[:10])
            try:
                count = self._fetch_faxes_from_rc(rc_config, date_from, date_to=date_to)
                total_imported += count
                ICP.set_param(chunk_key, 'done')
            except Exception:
                _logger.exception("Fax Import: chunk failed, will retry next run.")

        _logger.info("Fax Historical Import complete: %d total imported.", total_imported)

    @api.model
    def _fetch_faxes_from_rc(self, rc_config, date_from, date_to=None):
        """Fetch inbound faxes from RingCentral and create records. Returns import count."""
        import time as _time
        ICP = self.env['ir.config_parameter'].sudo()
        total_imported = 0
        total_skipped = 0

        params = {
            'messageType': 'Fax',
            'direction': 'Inbound',
            'dateFrom': date_from,
            'perPage': '100',
        }
        if date_to:
            params['dateTo'] = date_to

        endpoint = '/restapi/v1.0/account/~/extension/~/message-store'

        page = 1
        while True:
            params['page'] = str(page)
            data = rc_config._api_get(endpoint, params=params)

            records = data.get('records', [])
            if not records:
                break

            for msg in records:
                msg_id = str(msg.get('id', ''))
                if not msg_id:
                    continue
                if self.search_count([('ringcentral_message_id', '=', msg_id)]):
                    total_skipped += 1
                    continue
                if self._import_inbound_fax(msg, rc_config):
                    total_imported += 1

            paging = data.get('paging', {})
            if page >= paging.get('totalPages', 1):
                break
            page += 1
            _time.sleep(2)

        if not date_to:
            ICP.set_param(
                'fusion_faxes.last_inbound_poll',
                datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.000Z'),
            )

        if total_imported:
            _logger.info(
                "Fusion Faxes: Imported %d inbound faxes, skipped %d duplicates.",
                total_imported, total_skipped,
            )
        return total_imported

    def _import_inbound_fax(self, msg, rc_config):
        """Import a single inbound fax message dict from RingCentral."""
        try:
            import requests as _requests

            msg_id = str(msg.get('id', ''))
            from_info = msg.get('from', {})
            sender = from_info.get('phoneNumber', '') if isinstance(from_info, dict) else ''
            creation_time = msg.get('creationTime', '')
            read_status = msg.get('readStatus', '')
            page_count = msg.get('faxPageCount', 0)
            attachments = msg.get('attachments', [])

            received_dt = False
            if creation_time:
                try:
                    clean_time = creation_time.replace('Z', '+00:00')
                    received_dt = datetime.fromisoformat(clean_time).strftime('%Y-%m-%d %H:%M:%S')
                except (ValueError, AttributeError):
                    pass

            partner = False
            if sender:
                partner = self.env['res.partner'].sudo().search(
                    [('x_ff_fax_number', '=', sender)], limit=1
                )

            document_lines = []
            headers = rc_config._get_headers()
            for att in attachments:
                att_uri = att.get('uri', '')
                att_type = att.get('contentType', '')
                if not att_uri:
                    continue
                try:
                    resp = _requests.get(
                        att_uri,
                        headers=headers,
                        timeout=30,
                        verify=rc_config.ssl_verify,
                        proxies=rc_config._get_proxies(),
                    )
                    resp.raise_for_status()
                    pdf_content = resp.content
                    if not pdf_content:
                        continue

                    file_ext = 'pdf' if 'pdf' in (att_type or '') else 'bin'
                    file_name = f'FAX_IN_{msg_id}.{file_ext}'

                    ir_attachment = self.env['ir.attachment'].sudo().create({
                        'name': file_name,
                        'type': 'binary',
                        'datas': base64.b64encode(pdf_content),
                        'mimetype': att_type or 'application/pdf',
                        'res_model': 'fusion.fax',
                    })
                    document_lines.append((0, 0, {
                        'sequence': 10,
                        'attachment_id': ir_attachment.id,
                    }))
                except Exception:
                    _logger.exception("Fusion Faxes: Failed to download attachment for message %s", msg_id)

            self.sudo().create({
                'direction': 'inbound',
                'state': 'received',
                'fax_number': sender or 'Unknown',
                'sender_number': sender,
                'partner_id': partner.id if partner else False,
                'ringcentral_message_id': msg_id,
                'received_date': received_dt,
                'page_count': page_count or 0,
                'rc_read_status': read_status,
                'document_ids': document_lines,
            })
            return True

        except Exception:
            _logger.exception("Fusion Faxes: Failed to import inbound fax message.")
            return False
