<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Libraries\CloudTalkService;
use App\Libraries\OpenAiService;
use App\Models\CloudTalkConfigModel;
use App\Models\LeadCallModel;
use App\Models\LeadCallRecordingModel;
use App\Models\LeadCallTranscriptModel;
use Throwable;

class CloudTalkWebhook extends BaseController
{
    public function health()
    {
        return $this->response->setJSON([
            'ok'        => true,
            'service'   => 'CloudTalk Webhook Receiver',
            'timestamp' => gmdate('c'),
        ]);
    }

    public function handle()
    {
        $rawBody = (string) $this->request->getBody();
        if ($rawBody === '') {
            return $this->failValidation('Empty webhook payload.');
        }

        $rawJson = json_decode($rawBody, true);
        if (!is_array($rawJson)) {
            return $this->failValidation('Invalid JSON payload.');
        }

        try {
            $cfgModel = new CloudTalkConfigModel();
            $secret = (string) $cfgModel->getValue('webhook_signing_secret', '');
            if ($secret !== '') {
                $received = '';
                $headerNames = ['X-CloudTalk-Signature', 'X-Signature', 'Cloudtalk-Signature', 'Signature'];
                foreach ($headerNames as $h) {
                    $val = $this->request->getHeaderLine($h);
                    if ($val !== '') { $received = $val; break; }
                }
                $svc = new CloudTalkService();
                if (!$svc->verifyWebhookSignature($rawBody, $received, $secret)) {
                    return $this->failValidation('Webhook signature verification failed.', 401);
                }
            }
        } catch (Throwable $e) {}

        $event    = $this->extractEvent($rawJson);
        $callId   = $this->extractCallId($rawJson);
        $external = $this->extractExternalId($rawJson);

        if ($event === '' || $callId === '') {
            $data = [
                'event'   => $event,
                'call_id' => $callId,
                'external_id' => $external,
                'raw'     => $rawJson,
            ];
            log_message('info', 'CloudTalk webhook: missing event or call_id. Payload: ' . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
            return $this->response->setJSON(['ok' => true, 'handled' => false, 'reason' => 'missing_fields']);
        }

        try {
            $this->handleEvent($event, $callId, $external, $rawJson);
        } catch (Throwable $e) {
            log_message('error', 'CloudTalk webhook handler error [' . $event . ']: ' . $e->getMessage());
            return $this->response->setStatusCode(500)->setJSON(['ok' => false, 'error' => $e->getMessage()]);
        }

        return $this->response->setJSON(['ok' => true, 'handled' => true, 'event' => $event, 'call_id' => $callId]);
    }

    private function handleEvent(string $event, string $callId, ?string $externalId, array $payload): void
    {
        $callModel = new LeadCallModel();
        $call = $callModel->findByCloudTalkCallId($callId);
        $rawJson = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '';

        $updates = $this->extractCallUpdateFields($payload);
        if ($rawJson !== '') {
            $updates['raw_json'] = $rawJson;
        }

        if ($call === null) {
            $leadId = 0;
            if (ctype_digit((string) $externalId)) {
                $leadId = (int) $externalId;
            }
            $agentId = 0;
            $agentEmail = (string) ($updates['agent_email'] ?? $payload['agent']['email'] ?? $payload['agent_email'] ?? '');
            if ($agentEmail !== '') {
                try {
                    $userModel = new \App\Models\UserModel();
                    $u = $userModel->where('email', $agentEmail)->first();
                    if ($u) $agentId = (int) $u['id'];
                } catch (Throwable $e) {}
            }

            $insert = array_merge([
                'lead_id'              => $leadId > 0 ? $leadId : null,
                'agent_id'             => $agentId > 0 ? $agentId : null,
                'cloudtalk_call_id'    => $callId,
                'cloudtalk_contact_id' => (string) ($payload['contact_id'] ?? $payload['contact']['id'] ?? ''),
                'status'               => 'initiated',
            ], $updates);
            unset($insert['agent_email']);
            $callModel->insert($insert);
            $newId = (int) $callModel->getInsertID();
            if ($newId > 0) {
                $call = $callModel->find($newId);
            }
        } else {
            $id = (int) $call['id'];
            $existing = $call;
            $merge = array_merge([
                'cloudtalk_contact_id' => $existing['cloudtalk_contact_id'],
                'lead_id'              => $existing['lead_id'],
                'agent_id'             => $existing['agent_id'],
            ], $updates);
            if ((int)($existing['agent_id'] ?? 0) <= 0 && !empty($merge['agent_email'])) {
                try {
                    $userModel = new \App\Models\UserModel();
                    $u = $userModel->where('email', (string) $merge['agent_email'])->first();
                    if ($u) $merge['agent_id'] = (int) $u['id'];
                } catch (Throwable $e) {}
            }
            if (isset($merge['agent_email'])) unset($merge['agent_email']);
            if ((int)($existing['lead_id'] ?? 0) <= 0 && ctype_digit((string) $externalId)) {
                $merge['lead_id'] = (int) $externalId;
            }
            $callModel->update($id, $merge);
            $call = $callModel->find($id);
        }

        if ($call === null) return;
        $localCallId = (int) $call['id'];

        $recording = $this->extractRecording($payload);
        if ($recording !== null) {
            try {
                $recModel = new LeadCallRecordingModel();
                $existingRec = $recModel->findByCallId($localCallId);
                $recPayload = array_merge($recording, [
                    'call_id' => $localCallId,
                    'raw_json'=> json_encode(
                        isset($payload['recording']) ? ['recording' => $payload['recording']] + $payload : $payload,
                        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
                    ) ?: null,
                ]);
                if ($existingRec === null) {
                    $recModel->insert($recPayload);
                } else {
                    $recModel->update((int)$existingRec['id'], $recPayload);
                }
            } catch (Throwable $e) {
                log_message('warning', 'CloudTalk webhook recording upsert failed: ' . $e->getMessage());
            }
        }

        $transcript = $this->extractTranscript($payload);
        if ($transcript !== null) {
            try {
                $trModel = new LeadCallTranscriptModel();
                $existingTr = $trModel->findByCallId($localCallId);
                $trPayload = [
                    'call_id'            => $localCallId,
                    'transcript_text'    => (string) $transcript['transcript_text'],
                    'speaker_segments'   => $transcript['speaker_segments'] ?? null,
                    'language'           => $transcript['language'] ?? 'en',
                    'ai_summary_status'  => $existingTr['ai_summary_status'] ?? 'pending',
                    'ai_summary'         => $existingTr['ai_summary'] ?? null,
                    'raw_json'           => json_encode(
                        isset($payload['transcript']) ? ['transcript' => $payload['transcript']] + $payload : $payload,
                        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
                    ) ?: null,
                ];
                if ($existingTr === null) {
                    $trId = $trModel->insert($trPayload);
                    $existingTr = $trModel->find((int) $trId);
                } else {
                    $trModel->update((int)$existingTr['id'], $trPayload);
                    $existingTr = $trModel->find((int)$existingTr['id']);
                }

                try {
                    $cfg = new CloudTalkConfigModel();
                    $auto = (string) $cfg->getValue('auto_summarize', '1');
                    if ($auto === '1' && $existingTr && in_array((string)($existingTr['ai_summary_status'] ?? 'pending'), ['pending', 'error'], true)) {
                        $this->triggerAutoSummary((int)$existingTr['id']);
                    }
                } catch (Throwable $e2) {
                    log_message('warning', 'CloudTalk auto-summarize trigger failed: ' . $e2->getMessage());
                }
            } catch (Throwable $e) {
                log_message('warning', 'CloudTalk webhook transcript upsert failed: ' . $e->getMessage());
            }
        }
    }

    private function triggerAutoSummary(int $transcriptId): void
    {
        $trModel = new LeadCallTranscriptModel();
        $tr = $trModel->find($transcriptId);
        if (!$tr) return;

        $text = trim((string) ($tr['transcript_text'] ?? ''));
        if ($text === '') return;

        try {
            $trModel->update($transcriptId, ['ai_summary_status' => 'processing']);
        } catch (Throwable $e) {}

        $messages = [
            ['role' => 'system', 'content' => 'You are a helpful sales intelligence assistant reading a call transcript between an outbound agent and a lead.'],
            ['role' => 'user',   'content' => "Summarize this call in 5-7 bullet points. Include: customer sentiment, key topics discussed, objections, commitments made, and overall outcome.\n\nCall Transcript:\n" . $text],
        ];

        try {
            $openAi = new OpenAiService();
            $summary = $openAi->chatCompletions($messages);
            if ($summary !== '') {
                $trModel->update($transcriptId, ['ai_summary' => $summary, 'ai_summary_status' => 'done']);
            } else {
                $trModel->update($transcriptId, ['ai_summary_status' => 'error']);
            }
        } catch (Throwable $e) {
            try {
                $trModel->update($transcriptId, ['ai_summary_status' => 'error']);
            } catch (Throwable $e2) {}
            log_message('warning', 'CloudTalk auto-summarize OpenAI failed: ' . $e->getMessage());
        }
    }

    private function extractEvent(array $p): string
    {
        $e = (string) ($p['event'] ?? $p['type'] ?? $p['event_type'] ?? $p['webhook_event'] ?? '');
        return strtolower(trim($e));
    }

    private function extractCallId(array $p): string
    {
        return (string) ($p['call_id'] ?? $p['call']['id'] ?? $p['data']['call_id'] ?? $p['data']['id'] ?? $p['id'] ?? '');
    }

    private function extractExternalId(array $p): ?string
    {
        $v = $p['external_id'] ?? $p['call']['external_id'] ?? $p['data']['external_id'] ?? $p['contact']['external_id'] ?? null;
        return $v === null || $v === '' ? null : (string) $v;
    }

    private function extractCallUpdateFields(array $p): array
    {
        $call = is_array($p['call'] ?? null) ? $p['call'] : (is_array($p['data'] ?? null) ? $p['data'] : $p);
        $event = $this->extractEvent($p);
        $callModel = new LeadCallModel();

        $map = [];

        $status = (string) ($call['status'] ?? '');
        if ($status === '') {
            switch (true) {
                case str_contains($event, 'start'):
                    $status = 'ringing'; break;
                case str_contains($event, 'answer'):
                    $status = 'answered'; break;
                case str_contains($event, 'end') || str_contains($event, 'hangup'):
                    $status = 'ended'; break;
                case str_contains($event, 'missed'):
                    $status = 'missed'; break;
                case str_contains($event, 'fail') || str_contains($event, 'error'):
                    $status = 'failed'; break;
                case str_contains($event, 'cancel'):
                    $status = 'canceled'; break;
                default: $status = '';
            }
        }
        if ($status !== '') {
            $cleanStatus = $callModel->sanitizeCallStatus($status, '');
            if ($cleanStatus !== '') $map['status'] = $cleanStatus;
        }

        if (!empty($call['direction'])) $map['direction'] = (string) $call['direction'];
        if (!empty($call['from_number'])) $map['caller_number'] = (string) $call['from_number'];
        elseif (!empty($call['caller_number'])) $map['caller_number'] = (string) $call['caller_number'];
        elseif (!empty($call['source_number'])) $map['caller_number'] = (string) $call['source_number'];
        elseif (!empty($call['from'])) $map['caller_number'] = (string) $call['from'];

        if (!empty($call['to_number'])) $map['destination_number'] = (string) $call['to_number'];
        elseif (!empty($call['destination_number'])) $map['destination_number'] = (string) $call['destination_number'];
        elseif (!empty($call['to'])) $map['destination_number'] = (string) $call['to'];

        if (!empty($call['started_at'])) $map['started_at'] = $this->toMysqlDate($call['started_at']);
        elseif (!empty($call['start_time'])) $map['started_at'] = $this->toMysqlDate($call['start_time']);
        elseif (!empty($call['start'])) $map['started_at'] = $this->toMysqlDate($call['start']);
        elseif (!empty($call['t_started'])) $map['started_at'] = $this->toMysqlDate($call['t_started']);

        if (!empty($call['answered_at'])) $map['answered_at'] = $this->toMysqlDate($call['answered_at']);
        elseif (!empty($call['answer_time'])) $map['answered_at'] = $this->toMysqlDate($call['answer_time']);
        elseif (!empty($call['t_answered'])) $map['answered_at'] = $this->toMysqlDate($call['t_answered']);

        if (!empty($call['ended_at'])) $map['ended_at'] = $this->toMysqlDate($call['ended_at']);
        elseif (!empty($call['end_time'])) $map['ended_at'] = $this->toMysqlDate($call['end_time']);
        elseif (!empty($call['end'])) $map['ended_at'] = $this->toMysqlDate($call['end']);
        elseif (!empty($call['t_ended'])) $map['ended_at'] = $this->toMysqlDate($call['t_ended']);

        if (isset($call['duration'])) {
            $v = (int) $call['duration'];
            if ($v > 0) $map['duration_seconds'] = $v;
        }
        if (isset($call['total_duration'])) {
            $v = (int) $call['total_duration'];
            if ($v > 0) $map['duration_seconds'] = $v;
        }
        if (isset($call['talk_time']) || isset($call['billsec'])) {
            $v = (int) ($call['talk_time'] ?? $call['billsec'] ?? 0);
            if ($v > 0) $map['talk_time_seconds'] = $v;
        }
        if (isset($call['wrap_up_time']) || isset($call['wrapup_time'])) {
            $v = (int) ($call['wrap_up_time'] ?? $call['wrapup_time'] ?? 0);
            if ($v > 0) $map['wrap_up_time_seconds'] = $v;
        }

        if (!empty($call['result'])) $map['result'] = (string) $call['result'];
        if (!empty($call['disposition'])) $map['disposition'] = (string) $call['disposition'];
        if (!empty($call['agent_notes'])) $map['agent_notes'] = (string) $call['agent_notes'];
        if (!empty($call['notes'])) $map['agent_notes'] = (string) $call['notes'];

        $agentEmail = (string) ($p['agent_email'] ?? $p['agent']['email'] ?? $call['agent_email'] ?? $call['agent']['email'] ?? '');
        if ($agentEmail !== '') $map['agent_email'] = $agentEmail;

        return $map;
    }

    private function extractRecording(array $p): ?array
    {
        $rec = $p['recording'] ?? null;
        if (!is_array($rec)) {
            $data = $p['data'] ?? null;
            if (is_array($data) && isset($data['recording']) && is_array($data['recording'])) {
                $rec = $data['recording'];
            }
        }
        $url = '';
        if (is_array($rec)) {
            $url = (string) ($rec['url'] ?? $rec['recording_url'] ?? $rec['download_url'] ?? '');
            if ($url === '' && is_string($rec[0] ?? null)) $url = (string) $rec[0];
        } else {
            $url = (string) ($p['recording_url'] ?? '');
        }

        if ($url === '') return null;

        $out = ['recording_url' => $url];
        if (is_array($rec)) {
            if (!empty($rec['mime_type'])) $out['mime_type'] = (string) $rec['mime_type'];
            if (!empty($rec['file_size'])) $out['file_size'] = (int) $rec['file_size'];
            if (!empty($rec['duration']) || !empty($rec['duration_seconds'])) {
                $out['duration_seconds'] = (int) ($rec['duration_seconds'] ?? $rec['duration']);
            }
        }
        return $out;
    }

    private function extractTranscript(array $p): ?array
    {
        $tr = $p['transcript'] ?? null;
        if (!is_array($tr)) {
            $data = $p['data'] ?? null;
            if (is_array($data) && isset($data['transcript']) && is_array($data['transcript'])) {
                $tr = $data['transcript'];
            }
        }

        $text = '';
        $segments = null;
        $lang = 'en';

        if (is_string($tr)) {
            $text = $tr;
        } elseif (is_array($tr)) {
            $text = (string) ($tr['text'] ?? $tr['transcript'] ?? $tr['full_text'] ?? '');
            if ($text === '' && is_array($tr['segments'] ?? null)) {
                $parts = [];
                foreach ($tr['segments'] as $s) {
                    $speaker = (string) ($s['speaker'] ?? $s['role'] ?? '');
                    $t = (string) ($s['text'] ?? $s['content'] ?? '');
                    if ($t !== '') $parts[] = ($speaker !== '' ? $speaker . ': ' : '') . $t;
                }
                $text = implode("\n", $parts);
                $segments = json_encode($tr['segments'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            }
            if (!empty($tr['speaker_segments']) && $segments === null) {
                $seg = $tr['speaker_segments'];
                if (is_string($seg)) $segments = $seg;
                else $segments = json_encode($seg, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                if ($text === '' && is_array($tr['speaker_segments'] ?? null)) {
                    $parts = [];
                    foreach ($tr['speaker_segments'] as $s) {
                        $speaker = (string) ($s['speaker'] ?? $s['role'] ?? '');
                        $t = (string) ($s['text'] ?? $s['content'] ?? '');
                        if ($t !== '') $parts[] = ($speaker !== '' ? $speaker . ': ' : '') . $t;
                    }
                    $text = implode("\n", $parts);
                }
            }
            if (!empty($tr['language'])) $lang = (string) $tr['language'];
        }

        if ($text === '') {
            $text = (string) ($p['transcript_text'] ?? '');
        }

        if ($text === '') return null;
        return [
            'transcript_text'  => $text,
            'speaker_segments' => $segments,
            'language'         => $lang,
        ];
    }

    private function toMysqlDate(mixed $v): ?string
    {
        if ($v === null || $v === '') return null;
        if (is_numeric($v)) {
            $ts = (int) $v;
            if ($ts > 10000000000) $ts = (int) ($ts / 1000);
            if ($ts <= 0) return null;
            return gmdate('Y-m-d H:i:s', $ts);
        }
        $ts = strtotime((string) $v);
        if ($ts === false) return null;
        return gmdate('Y-m-d H:i:s', $ts);
    }

    private function failValidation(string $msg, int $code = 400)
    {
        log_message('warning', 'CloudTalk webhook rejected: ' . $msg);
        return $this->response->setStatusCode($code)->setJSON(['ok' => false, 'error' => $msg]);
    }
}
