<?php
require_once 'config/database.php';
require_once 'controllers/FollowUpsController.php';
require_once 'vendor/Twilio/autoload.php';

use Twilio\Jwt\AccessToken;
use Twilio\Jwt\Grants\VoiceGrant;
use Twilio\TwiML\VoiceResponse;

class TwilioCallController {
    private $conn;
    private $twilioClient;
    private $followUpsController;

    public function __construct() {
        $database = new Database();
        $this->conn = $database->getConnection();
        $this->followUpsController = new FollowUpsController();
        $this->twilioClient = new \Twilio\Rest\Client(
            getenv('TWILIO_ACCOUNT_SID'),
            getenv('TWILIO_AUTH_TOKEN')
        );
    }

    /**
     * ==========================================
     * 1) CREATE FOLLOW-UP (SAFE VERSION)
     * ==========================================
     */
    public function createFollowUp($data) {
        try {
            if (!isset($data['invoice_id'], $data['ipo_id'])) {
                return json_encode([
                    'status' => 'error',
                    'message' => 'Missing required fields'
                ]);
            }

            // Check (and lock) duplicates
            $checkQuery = "
                SELECT id FROM follow_ups
                WHERE invoice_id = :invoice_id AND ipo_id = :ipo_id
                LIMIT 1
                FOR UPDATE";

            $checkStmt = $this->conn->prepare($checkQuery);
            $checkStmt->bindParam(':invoice_id', $data['invoice_id']);
            $checkStmt->bindParam(':ipo_id', $data['ipo_id']);
            $checkStmt->execute();

            $existing = $checkStmt->fetch(PDO::FETCH_ASSOC);

            if ($existing) {
                return json_encode([
                    'status' => 'success',
                    'message' => 'Follow-up already exists',
                    'follow_up_id' => $existing['id']
                ]);
            }

            // Insert new follow-up
            $query = "INSERT INTO follow_ups 
                     (invoice_id, ipo_id, status, attempts, priority, notes, created_at, updated_at)
                     VALUES (:invoice_id, :ipo_id, 'active', 0, :priority, :notes, NOW(), NOW())";

            $priority = $data['priority'] ?? 'medium';
            $notes = $data['notes'] ?? null;

            $stmt = $this->conn->prepare($query);
            $stmt->bindParam(':invoice_id', $data['invoice_id']);
            $stmt->bindParam(':ipo_id', $data['ipo_id']);
            $stmt->bindParam(':priority', $priority);
            $stmt->bindParam(':notes', $notes);

            if ($stmt->execute()) {
                return json_encode([
                    'status' => 'success',
                    'message' => 'Follow-up created',
                    'follow_up_id' => $this->conn->lastInsertId()
                ]);
            }

            return json_encode([
                'status' => 'error',
                'message' => 'Failed to create follow-up'
            ]);

        } catch (Exception $e) {
            return json_encode([
                'status' => 'error',
                'message' => 'Exception: ' . $e->getMessage()
            ]);
        }
    }




    /**
     * ==========================================
     * 2) RECORD CALL (SAFE VERSION)
     * ==========================================
     */
    public function recordCall($data) {
        try {
            if (!isset($data['follow_up_id'], $data['start_time'], $data['status'])) {
                return json_encode([
                    'status' => 'error',
                    'message' => 'Missing required fields'
                ]);
            }

            $duration = $data['duration'] ?? null;
            $callSid = $data['call_sid'] ?? null;
            $failure_reason = $data['failure_reason'] ?? null;
            $recording_file = $data['recording_file'] ?? null;

            // Insert call record
            $query = "INSERT INTO calls 
                     (follow_up_id, call_sid, start_time, duration, status, failure_reason, recording_file, created_at, updated_at)
                     VALUES (:follow_up_id, :call_sid, :start_time, :duration, :status, :failure_reason, :recording_file, NOW(), NOW())";

            $stmt = $this->conn->prepare($query);
            $stmt->bindParam(':follow_up_id', $data['follow_up_id']);
            $stmt->bindParam(':call_sid', $callSid);
            $stmt->bindParam(':start_time', $data['start_time']);
            $stmt->bindParam(':duration', $duration);
            $stmt->bindParam(':status', $data['status']);
            $stmt->bindParam(':failure_reason', $failure_reason);
            $stmt->bindParam(':recording_file', $recording_file);

            if (!$stmt->execute()) {
                return json_encode([
                    'status' => 'error',
                    'message' => 'Error recording call'
                ]);
            }

            return json_encode([
                'status' => 'success',
                'message' => 'Call recorded',
                'call_id' => $this->conn->lastInsertId()
            ]);

        } catch (Exception $e) {
            return json_encode([
                'status' => 'error',
                'message' => 'Exception: ' . $e->getMessage()
            ]);
        }
    }




    /**
     * ==========================================
     * 3) INITIATE CALL (COMPLETE + FIXED)
     * ==========================================
     */
    public function initiateCall($data) {
        try {

            if (!isset($data['invoice_id'], $data['ipo_id'], $data['to'])) {
                return json_encode([
                    'status' => 'error',
                    'message' => 'Missing required fields'
                ]);
            }

            $this->conn->beginTransaction();


            /**
             * Step 1: Check for existing follow-up (LOCK ROW)
             */
            $checkQuery = "
                SELECT id FROM follow_ups
                WHERE invoice_id = :invoice_id
                AND ipo_id = :ipo_id
                AND status IN ('active','pending','in_progress')
                LIMIT 1
                FOR UPDATE";

            $checkStmt = $this->conn->prepare($checkQuery);
            $checkStmt->bindParam(':invoice_id', $data['invoice_id']);
            $checkStmt->bindParam(':ipo_id', $data['ipo_id']);
            $checkStmt->execute();
            $existing = $checkStmt->fetch(PDO::FETCH_ASSOC);

            if ($existing) {
                // Update attempt count
                $updateQuery = "UPDATE follow_ups 
                                SET attempts = attempts + 1,
                                    last_contact = NOW(),
                                    updated_at = NOW()
                                WHERE id = :id";

                $updateStmt = $this->conn->prepare($updateQuery);
                $updateStmt->bindParam(':id', $existing['id']);
                $updateStmt->execute();

                $followUpId = $existing['id'];
            } else {
                // Create new follow-up
                $resp = json_decode($this->createFollowUp([
                    'invoice_id' => $data['invoice_id'],
                    'ipo_id' => $data['ipo_id'],
                    'priority' => $data['priority'] ?? 'medium',
                    'notes' => 'Call initiated to ' . $data['to']
                ]), true);

                if ($resp['status'] !== 'success') {
                    $this->conn->rollBack();
                    return json_encode([
                        'status' => 'error',
                        'message' => $resp['message']
                    ]);
                }

                $followUpId = $resp['follow_up_id'];
            }

            /**
             * Step 2: Initiate Twilio Call
             */
            // $call = $this->twilioClient->calls->create(
            //     $data['to'],
            //     getenv('TWILIO_PHONE_NUMBER'),
            //     [
            //         'url' => getenv('APP_URL') . '/voice-outgoing?followUpId=' . $followUpId,
            //         'statusCallback' => getenv('APP_URL') . '/calls-status',
            //         'statusCallbackEvent' => ['initiated', 'ringing', 'in-progress', 'busy', 'failed', 'answered', 'completed'],
            //         'statusCallbackMethod' => 'POST',
            //         'record' => true,
            //         'recordingStatusCallback' => getenv('APP_URL') . '/recording-status',
            //         'recordingStatusCallbackMethod' => 'POST',
            //         'timeout' => 30
            //     ]
            // );

            /**
             * Step 3: Store call data
             */
            $callData = [
                'follow_up_id' => $followUpId,
                // 'call_sid' => $call->sid,
                'start_time' => date('Y-m-d H:i:s'),
                'status' => 'initiating',
                'duration' => null,
                'failure_reason' => null,
                'recording_file' => null
            ];

            $recordResp = json_decode($this->recordCall($callData), true);

            if ($recordResp['status'] !== 'success') {
                $this->conn->rollBack();
                return json_encode([
                    'status' => 'error',
                    'message' => 'Failed to store call: ' . $recordResp['message']
                ]);
            }


            $this->conn->commit();

            return json_encode([
                'status' => 'success',
                'message' => 'Call initiated',
                'data' => [
                    // 'callSid' => $call->sid,
                    'follow_up_id' => $followUpId,
                    'call_id' => $recordResp['call_id']
                ]
            ]);

        } catch (\Twilio\Exceptions\TwilioException $e) {
            $this->conn->rollBack();
            return json_encode(['status' => 'error', 'message' => 'Twilio Error: ' . $e->getMessage()]);

        } catch (Exception $e) {
            $this->conn->rollBack();
            return json_encode(['status' => 'error', 'message' => 'Exception: ' . $e->getMessage()]);
        }
    }

    /**
     * Voice outgoing webhook - provides TwiML for outbound calls
     */
    
    public function voiceOutgoing() {
        try {
            // Clean ALL previous output
            while (ob_get_level()) {
                ob_end_clean();
            }
            ob_start();

            // Force text/xml — clear any Content-Type set earlier (e.g. application/json)
            if (function_exists('header_remove')) { header_remove('Content-Type'); }
            header('Content-Type: text/xml; charset=utf-8', true);

            // Log incoming request safely (never output)
            $logFile = __DIR__ . '/twilio_logs.txt';
            $logData = date('Y-m-d H:i:s') . " - Incoming request: " . json_encode($_REQUEST) . PHP_EOL;
            file_put_contents($logFile, $logData, FILE_APPEND);

            $response = new VoiceResponse();
            $toNumber = $_REQUEST['To'] ?? null;

            if ($toNumber) {
                $dial = $response->dial('', ['callerId' => getenv('TWILIO_PHONE_NUMBER')]);
                $dial->number($toNumber);
            } else {
                $response->say('Sorry, no destination number provided.');
                $response->hangup();
            }

            echo $response;
            exit;   // VERY IMPORTANT
        } catch (Exception $e) {
            // Twilio fallback is plaintext, NOT XML
            header("Content-Type: text/plain");
            echo "Error generating TwiML: " . $e->getMessage();
            exit;
        }
    }

    public function callStatus($data)
    {
        try {
            // Clean output
            while (ob_get_level()) { ob_end_clean(); }
            header("Content-Type: text/plain");

            $callSid = $data['CallSid'] ?? null;
            $callStatus = $data['CallStatus'] ?? null;
            $duration = $data['CallDuration'] ?? null;

            // Log raw request
            file_put_contents(
                __DIR__ . '/calls_status_log.txt',
                date('Y-m-d H:i:s') . " - " . json_encode($data) . PHP_EOL,
                FILE_APPEND
            );

            if (!$callSid || !$callStatus) {
                echo "Missing CallSid or CallStatus";
                return;
            }

            // Map Twilio statuses to DB-friendly values
            $statusMap = [
                'queued' => 'queued',
                'initiated' => 'initiated',
                'ringing' => 'ringing',
                'in-progress' => 'in_progress',
                'completed' => 'completed',
                'busy' => 'failed',
                'failed' => 'failed',
                'no-answer' => 'no_answer'
            ];

            $dbStatus = $statusMap[$callStatus] ?? 'unknown';

            // Update calls table
            $updateQuery = "
                UPDATE calls 
                SET status = :status,
                    duration = :duration,
                    updated_at = NOW()
                WHERE call_sid = :call_sid
            ";
            $stmt = $this->conn->prepare($updateQuery);
            $stmt->bindValue(':status', $dbStatus);
            $stmt->bindValue(':duration', $duration);
            $stmt->bindValue(':call_sid', $callSid);
            $stmt->execute();

            // Update follow_up if call completed
            if (in_array($callStatus, ['completed', 'busy', 'failed', 'no-answer'])) {
                $followUpQuery = "
                    UPDATE follow_ups 
                    SET last_contact = NOW(), 
                        updated_at = NOW(),
                        attempts = attempts + 1
                    WHERE id = (
                        SELECT follow_up_id FROM calls WHERE call_sid = :call_sid LIMIT 1
                    )
                ";
                $fs = $this->conn->prepare($followUpQuery);
                $fs->bindValue(':call_sid', $callSid);
                $fs->execute();
            }

            echo "OK";
            return;

        } catch (\Exception $e) {
            echo "Error: " . $e->getMessage();
            return;
        }
    }


    public function recordingStatus($data)
    {
        try {
            while (ob_get_level()) { ob_end_clean(); }

            header("Content-Type: text/plain");

            $recordingUrl = $data['RecordingUrl'] ?? null;
            $recordingSid = $data['RecordingSid'] ?? null;
            $callSid = $data['CallSid'] ?? null;
            $status = $data['RecordingStatus'] ?? null;

            file_put_contents(
                __DIR__ . '/recording_status_log.txt',
                date('Y-m-d H:i:s') . " - " . json_encode($data) . PHP_EOL,
                FILE_APPEND
            );

            if (!$recordingUrl || !$callSid) {
                echo "Missing data";
                return;
            }

            // Save recording to DB
            $query = "
                UPDATE calls 
                SET recording_file = :file
                WHERE call_sid = :call_sid
            ";

            $stmt = $this->conn->prepare($query);
            $stmt->bindValue(':file', $recordingUrl . '.mp3'); 
            $stmt->bindValue(':call_sid', $callSid);
            $stmt->execute();

            echo "OK";
            return;

        } catch (\Exception $e) {
            echo "Error: " . $e->getMessage();
            return;
        }
    }


    /**
     * Ending an active Twilio call
     */
    public function endCall($data) {
        try {
            if (!isset($data['callSid'])) {
                return json_encode(['status' => 'error', 'message' => 'Missing callSid']);
            }

            $this->conn->beginTransaction();

            $callSid = $data['callSid'];
            $this->twilioClient->calls($callSid)->update(['status' => 'completed']);

            // Update call record
            $query = "UPDATE calls SET status = 'completed', updated_at = NOW() WHERE call_sid = :call_sid";
            $stmt = $this->conn->prepare($query);
            $stmt->bindParam(':call_sid', $callSid);

            if ($stmt->execute()) {
                $this->conn->commit();
                return json_encode(['status' => 'success', 'message' => 'Call ended']);
            } else {
                $this->conn->rollBack();
                return json_encode(['status' => 'error', 'message' => 'Error ending call']);
            }
        } catch (\Twilio\Exceptions\TwilioException $e) {
            $this->conn->rollBack();
            return json_encode(['status' => 'error', 'message' => 'Twilio Exception: ' . $e->getMessage()]);
        } catch (Exception $e) {
            $this->conn->rollBack();
            return json_encode(['status' => 'error', 'message' => 'Exception: ' . $e->getMessage()]);
        }
    }

    public function callToken($identity){
        $accountSid = getenv('TWILIO_ACCOUNT_SID');
        $apiKeySid  = getenv('TWILIO_API_KEY_SID');
        $apiKeySecret = getenv('TWILIO_API_KEY_SECRET');
        $twimlAppSid = getenv('TWILIO_TWIML_APP_SID');

        $token = new AccessToken($accountSid, $apiKeySid, $apiKeySecret, 3600, $identity);

        $voiceGrant = new VoiceGrant();
        $voiceGrant->setOutgoingApplicationSid($twimlAppSid);
        $voiceGrant->setIncomingAllow(true);
        $token->addGrant($voiceGrant);

        return json_encode(['status'=>'success','token'=>$token->toJWT(),'identity'=>$identity]);
    }

    /**
     * Testing Twilio call functionality
     */
    public function testCall() {
        try {
            $sid = getenv("TWILIO_ACCOUNT_SID");
            $token = getenv("TWILIO_AUTH_TOKEN");
            $from = getenv("TWILIO_PHONE_NUMBER");
            $callerId = getenv("TWILIO_CALLER_ID");

            $twilio = new \Twilio\Rest\Client($sid, $token);

            $call = $twilio->calls->create(
                $callerId,
                $from,
                ["url" => "http://demo.twilio.com/docs/voice.xml"]
            );

            return json_encode([
                'status' => 'success',
                'message' => 'Test call initiated',
                'callSid' => $call->sid,
                'response' => $call
            ]);
        } catch (\Twilio\Exceptions\TwilioException $e) {
            return json_encode([
                'status' => 'error',
                'message' => 'Twilio Exception: ' . $e->getMessage()
            ]);
        } catch (\Exception $e) {
            return json_encode([
                'status' => 'error',
                'message' => 'Exception: ' . $e->getMessage()
            ]);
        }
    }
}
