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

import logging
import re
from datetime import datetime, timedelta

from odoo import api, fields, models, _

_logger = logging.getLogger(__name__)


class RcCallHistory(models.Model):
    _name = 'rc.call.history'
    _description = 'RingCentral Call History'
    _inherit = ['mail.thread']
    _order = 'start_time desc'
    _rec_name = 'name'

    name = fields.Char(
        string='Reference',
        required=True,
        copy=False,
        readonly=True,
        default=lambda self: _('New'),
    )
    rc_session_id = fields.Char(string='RC Session ID', index=True, readonly=True)
    direction = fields.Selection([
        ('inbound', 'Inbound'),
        ('outbound', 'Outbound'),
    ], string='Direction', required=True, tracking=True)
    from_number = fields.Char(string='From Number')
    to_number = fields.Char(string='To Number')
    start_time = fields.Datetime(string='Start Time', index=True)
    duration = fields.Integer(string='Duration (sec)')
    duration_display = fields.Char(string='Duration', compute='_compute_duration_display')
    status = fields.Selection([
        ('answered', 'Answered'),
        ('no_answer', 'No Answer'),
        ('busy', 'Busy'),
        ('missed', 'Missed'),
        ('voicemail', 'Voicemail'),
        ('rejected', 'Rejected'),
        ('unknown', 'Unknown'),
    ], string='Status', default='unknown', tracking=True)

    partner_id = fields.Many2one('res.partner', string='Contact', tracking=True)
    user_id = fields.Many2one('res.users', string='Odoo User', default=lambda self: self.env.user)

    sale_order_count = fields.Integer(string='Sales Orders', compute='_compute_partner_counts')
    invoice_count = fields.Integer(string='Invoices', compute='_compute_partner_counts')

    has_recording = fields.Boolean(string='Has Recording', default=False)
    recording_url = fields.Char(string='Recording URL')
    recording_content_uri = fields.Char(string='Recording Content URI')

    has_transcript = fields.Boolean(string='Has Transcript', default=False)
    transcript_text = fields.Text(string='Transcript')

    sale_order_id = fields.Many2one('sale.order', string='Sale Order', ondelete='set null')
    notes = fields.Text(string='Notes')

    @api.depends('partner_id')
    def _compute_partner_counts(self):
        for rec in self:
            if rec.partner_id:
                partner = rec.partner_id.commercial_partner_id or rec.partner_id
                partner_ids = (partner | partner.child_ids).ids
                rec.sale_order_count = self.env['sale.order'].search_count(
                    [('partner_id', 'in', partner_ids)]
                )
                rec.invoice_count = self.env['account.move'].search_count(
                    [('partner_id', 'in', partner_ids),
                     ('move_type', 'in', ('out_invoice', 'out_refund'))]
                )
            else:
                rec.sale_order_count = 0
                rec.invoice_count = 0

    @api.depends('duration')
    def _compute_duration_display(self):
        for rec in self:
            if rec.duration:
                minutes, seconds = divmod(rec.duration, 60)
                rec.duration_display = f'{minutes}m {seconds}s' if minutes else f'{seconds}s'
            else:
                rec.duration_display = '0s'

    @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('rc.call.history') or _('New')
            if not vals.get('partner_id'):
                partner = self._match_partner(
                    vals.get('from_number', ''),
                    vals.get('to_number', ''),
                    vals.get('direction', ''),
                )
                if partner:
                    vals['partner_id'] = partner.id
        return super().create(vals_list)

    @api.model
    def _match_partner(self, from_number, to_number, direction):
        """Auto-link a call to a partner by matching phone numbers.

        For outbound calls, match the to_number (who we called).
        For inbound calls, match the from_number (who called us).
        Compares last 10 digits to handle +1, (905), etc.
        """
        search_number = to_number if direction == 'outbound' else from_number
        if not search_number:
            return False

        cleaned = self._normalize_phone(search_number)
        if not cleaned or len(cleaned) < 7:
            return False

        Partner = self.env['res.partner']
        phone_fields = [f for f in ('phone', 'mobile') if f in Partner._fields]
        if not phone_fields:
            return False

        domain = ['|'] * (len(phone_fields) - 1)
        for f in phone_fields:
            domain.append((f, '!=', False))
        partners = Partner.search(domain, order='customer_rank desc, write_date desc')

        for p in partners:
            for f in phone_fields:
                val = p[f]
                if val and self._normalize_phone(val) == cleaned:
                    return p

        return False

    @staticmethod
    def _normalize_phone(number):
        """Strip a phone number to last 10 digits for comparison."""
        return re.sub(r'\D', '', number or '')[-10:] if number else ''

    # ──────────────────────────────────────────────────────────
    # Smart button actions
    # ──────────────────────────────────────────────────────────

    def action_view_contact(self):
        """Open the linked contact."""
        self.ensure_one()
        if not self.partner_id:
            return
        return {
            'type': 'ir.actions.act_window',
            'res_model': 'res.partner',
            'res_id': self.partner_id.id,
            'view_mode': 'form',
            'target': 'current',
        }

    def action_view_sale_orders(self):
        """Open sale orders for the linked contact."""
        self.ensure_one()
        if not self.partner_id:
            return
        partner = self.partner_id.commercial_partner_id or self.partner_id
        partner_ids = (partner | partner.child_ids).ids
        return {
            'type': 'ir.actions.act_window',
            'name': _('Sales Orders - %s') % self.partner_id.name,
            'res_model': 'sale.order',
            'view_mode': 'list,form',
            'domain': [('partner_id', 'in', partner_ids)],
            'target': 'current',
        }

    def action_view_invoices(self):
        """Open invoices for the linked contact."""
        self.ensure_one()
        if not self.partner_id:
            return
        partner = self.partner_id.commercial_partner_id or self.partner_id
        partner_ids = (partner | partner.child_ids).ids
        return {
            'type': 'ir.actions.act_window',
            'name': _('Invoices - %s') % self.partner_id.name,
            'res_model': 'account.move',
            'view_mode': 'list,form',
            'domain': [('partner_id', 'in', partner_ids),
                        ('move_type', 'in', ('out_invoice', 'out_refund'))],
            'target': 'current',
        }

    def _get_contact_number(self):
        """Return the external phone number for this call."""
        self.ensure_one()
        if self.direction == 'outbound':
            return self.to_number or self.from_number
        return self.from_number or self.to_number

    def action_make_call(self):
        """Trigger a call via the RingCentral Embeddable widget (handled by JS)."""
        self.ensure_one()
        return {
            'type': 'ir.actions.act_window_close',
            'infos': {
                'rc_action': 'call',
                'rc_phone_number': self._get_contact_number(),
            },
        }

    def action_send_sms(self):
        """Open the RingCentral Embeddable SMS compose (handled by JS)."""
        self.ensure_one()
        return {
            'type': 'ir.actions.act_window_close',
            'infos': {
                'rc_action': 'sms',
                'rc_phone_number': self._get_contact_number(),
            },
        }

    def action_send_fax(self):
        """Open the Send Fax wizard pre-filled with this call's contact."""
        self.ensure_one()
        ctx = {}
        if self.partner_id:
            ctx['default_partner_id'] = self.partner_id.id
            fax_num = getattr(self.partner_id, 'x_ff_fax_number', '')
            if fax_num:
                ctx['default_fax_number'] = fax_num
        return {
            'type': 'ir.actions.act_window',
            'name': _('Send Fax'),
            'res_model': 'fusion_faxes.send.fax.wizard',
            'view_mode': 'form',
            'target': 'new',
            'context': ctx,
        }

    # ──────────────────────────────────────────────────────────
    # Recording playback
    # ──────────────────────────────────────────────────────────

    def action_play_recording(self):
        """Open the recording proxy URL in a new window."""
        self.ensure_one()
        if not self.has_recording:
            return
        base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url', '')
        return {
            'type': 'ir.actions.act_url',
            'url': f'{base_url}/ringcentral/recording/{self.id}',
            'target': 'new',
        }

    # ──────────────────────────────────────────────────────────
    # Cron: sync call history from RingCentral
    # ──────────────────────────────────────────────────────────

    @api.model
    def _cron_sync_call_history(self):
        """Incremental sync: only fetch calls since last successful sync.

        Uses `fusion_rc.last_call_sync` to know where to start.
        Only queries RingCentral for new records, skips everything
        already imported.  Runs every 15 min by default.
        """
        config = self.env['rc.config']._get_active_config()
        if not config:
            return

        ICP = self.env['ir.config_parameter'].sudo()
        last_sync = ICP.get_param('fusion_rc.last_call_sync', '')

        if not last_sync:
            two_days_ago = datetime.utcnow() - timedelta(days=2)
            last_sync = two_days_ago.strftime('%Y-%m-%dT%H:%M:%S.000Z')

        now_str = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.000Z')
        total_imported = self._sync_calls_from_date(config, last_sync, now_str)

        ICP.set_param('fusion_rc.last_call_sync', now_str)

        if total_imported:
            _logger.info(
                "RC Incremental Sync: %d new calls imported (from %s).",
                total_imported, last_sync[:16],
            )

    @api.model
    def _cron_daily_catchup_sync(self):
        """Daily catchup: re-scan the past 3 days to catch anything missed.

        Covers calls made from physical phones, mobile app, or any source
        outside Odoo.  Deduplicates by rc_session_id so no duplicates
        are created.  Runs once per day.
        """
        config = self.env['rc.config']._get_active_config()
        if not config:
            return

        three_days_ago = datetime.utcnow() - timedelta(days=3)
        date_from = three_days_ago.strftime('%Y-%m-%dT%H:%M:%S.000Z')
        now_str = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.000Z')

        total_imported = self._sync_calls_from_date(config, date_from, now_str)
        _logger.info(
            "RC Daily Catchup: %d new calls imported (past 3 days).",
            total_imported,
        )

    def _sync_calls_from_date(self, config, date_from, date_to=None):
        """Fetch call records from RingCentral for a date range.

        Skips records whose rc_session_id already exists in our DB
        (checked via a pre-loaded set, not per-record SQL queries).
        Returns the total number of newly imported records.
        """
        total_imported = 0
        total_skipped = 0
        try:
            params = {
                'dateFrom': date_from,
                'type': 'Voice',
                'view': 'Detailed',
                'perPage': 250,
            }
            if date_to:
                params['dateTo'] = date_to

            data = config._api_get(
                '/restapi/v1.0/account/~/extension/~/call-log', params=params,
            )
            imported, skipped = self._process_call_page(data)
            total_imported += imported
            total_skipped += skipped

            while data.get('navigation', {}).get('nextPage', {}).get('uri'):
                next_uri = data['navigation']['nextPage']['uri']
                data = config._api_get(next_uri)
                imported, skipped = self._process_call_page(data)
                total_imported += imported
                total_skipped += skipped

        except Exception:
            _logger.exception("Fusion RingCentral: Error syncing call history.")

        if total_skipped:
            _logger.debug("RC Sync: skipped %d already-imported records.", total_skipped)
        return total_imported

    def _process_call_page(self, data):
        """Process a single page of call log records.

        Uses a bulk session_id lookup (one SQL query per page)
        instead of per-record queries.
        Returns (imported_count, skipped_count).
        """
        records = data.get('records', [])
        if not records:
            return 0, 0

        page_session_ids = [
            r.get('sessionId', '') for r in records if r.get('sessionId')
        ]

        if page_session_ids:
            existing = set(
                r.rc_session_id
                for r in self.search([('rc_session_id', 'in', page_session_ids)])
            )
        else:
            existing = set()

        imported = 0
        skipped = 0
        for rec in records:
            session_id = rec.get('sessionId', '')
            if not session_id:
                continue
            if session_id in existing:
                skipped += 1
                continue
            self._import_call_record(rec)
            existing.add(session_id)
            imported += 1

        return imported, skipped

    def _import_call_record(self, rec):
        """Import a single call log record from the API."""
        try:
            direction_raw = rec.get('direction', '').lower()
            direction = 'inbound' if direction_raw == 'inbound' else 'outbound'

            from_info = rec.get('from', {})
            to_info = rec.get('to', {})
            from_number = from_info.get('phoneNumber', '') or from_info.get('name', '')
            to_number = to_info.get('phoneNumber', '') or to_info.get('name', '')

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

            result = rec.get('result', 'Unknown')
            status_map = {
                'Accepted': 'answered',
                'Call connected': 'answered',
                'Voicemail': 'voicemail',
                'Missed': 'missed',
                'No Answer': 'no_answer',
                'Busy': 'busy',
                'Rejected': 'rejected',
                'Hang Up': 'answered',
                'Reply': 'answered',
            }
            status = status_map.get(result, 'unknown')

            recording = rec.get('recording', {})
            has_recording = bool(recording)
            recording_content_uri = recording.get('contentUri', '') if recording else ''

            self.sudo().create({
                'rc_session_id': rec.get('sessionId', ''),
                'direction': direction,
                'from_number': from_number,
                'to_number': to_number,
                'start_time': start_time,
                'duration': rec.get('duration', 0),
                'status': status,
                'has_recording': has_recording,
                'recording_content_uri': recording_content_uri,
            })
        except Exception:
            _logger.exception("Failed to import call record: %s", rec.get('sessionId', ''))

    # ──────────────────────────────────────────────────────────
    # Cron: fetch transcripts
    # ──────────────────────────────────────────────────────────

    @api.model
    def _cron_fetch_transcripts(self):
        """Fetch AI transcripts for calls that have recordings but no transcript."""
        config = self.env['rc.config']._get_active_config()
        if not config:
            return

        calls = self.search([
            ('has_recording', '=', True),
            ('has_transcript', '=', False),
            ('recording_content_uri', '!=', False),
        ], limit=20)

        for call in calls:
            try:
                content_uri = call.recording_content_uri
                if not content_uri:
                    continue

                media_url = f'{content_uri}?access_token={config.access_token}'
                data = config._api_post(
                    '/ai/audio/v1/async/speech-to-text?webhook=false',
                    data={
                        'contentUri': media_url,
                        'source': 'CallRecording',
                    },
                )

                transcript = ''
                segments = data.get('utterances', [])
                for seg in segments:
                    speaker = seg.get('speakerName', 'Speaker')
                    text = seg.get('text', '')
                    transcript += f'{speaker}: {text}\n'

                if transcript:
                    call.write({
                        'has_transcript': True,
                        'transcript_text': transcript.strip(),
                    })
            except Exception:
                _logger.debug("Transcript not available yet for call %s", call.name)
