<?php

namespace App\Controllers\Api;

use App\Libraries\JwtAuth;
use CodeIgniter\HTTP\ResponseInterface;

class FinFloController extends BaseApiController
{
    private const PENDING_PREFIX = 'finflo_pending_';
    private const DONE_PREFIX    = 'finflo_done_';
    private const PENDING_TTL    = 900; // 15 min, match session expiry
    private const DONE_TTL       = 300; // 5 min for app to poll

    /**
     * POST api/payments/finflo/create-session
     * Body: { "amount": number }. Returns { "pay_url", "pending_id" } for Option 2 hosted checkout.
     */
    public function createSession(): ResponseInterface
    {
        log_message('info', '[FinFlo] createSession called');
        $user = JwtAuth::user();
        if (! $user) {
            log_message('info', '[FinFlo] createSession: Unauthorized');
            return $this->error('Unauthorized', 401);
        }

        $json  = $this->request->getJSON(true) ?: [];
        $amount = isset($json['amount']) ? (float) $json['amount'] : null;
        log_message('info', '[FinFlo] createSession: amount=' . ($amount !== null ? $amount : 'null') . ' callback_base=' . ($json['callback_base'] ?? 'not set'));
        if ($amount === null || $amount <= 0) {
            return $this->error('amount is required and must be positive', 400);
        }

        $config   = config('FinFlo');
        $apiKey   = env('FINFLO_API_KEY') ?? $config->apiKey;
        $appSlug  = env('FINFLO_APP_SLUG') ?: $config->appSlug;
        $baseUrl  = rtrim($config->baseUrl, '/');
        $endpoint = $baseUrl . '/api/payments/direct-debit/create-session';

        $pendingId = bin2hex(random_bytes(16));

        // Use callback_base from app when provided (e.g. ngrok URL) so redirect works on device; else use server base_url()
        $callbackBase = isset($json['callback_base']) && is_string($json['callback_base']) ? rtrim($json['callback_base'], '/') : null;
        helper('url');
        if ($callbackBase !== null && $callbackBase !== '') {
            $callbackUrl = $callbackBase . '/api/payments/finflo/callback?pending=' . $pendingId;
            $cancelUrl   = $callbackBase . '/api/payments/finflo/cancel?pending=' . $pendingId;
        } else {
            $callbackUrl = base_url('api/payments/finflo/callback') . '?pending=' . $pendingId;
            $cancelUrl   = base_url('api/payments/finflo/cancel') . '?pending=' . $pendingId;
        }
        log_message('info', '[FinFlo] createSession: pending_id=' . $pendingId . ' callback_url=' . $callbackUrl);

        cache()->save(self::PENDING_PREFIX . $pendingId, [
            'user_id' => $user->id,
            'amount'  => $amount,
        ], self::PENDING_TTL);

        $body = [
            'app_slug'     => $appSlug,
            'amount'       => (int) round($amount),
            'reference'    => $pendingId,
            'callback_url' => $callbackUrl,
            'cancel_url'   => $cancelUrl,
        ];

        $client  = service('curlrequest');
        $headers = [
            'Content-Type' => 'application/json',
            'Accept'       => 'application/json',
        ];
        if ($apiKey !== null && $apiKey !== '') {
            $headers['X-API-Key'] = $apiKey;
        }

        $requestOptions = [
            'headers' => $headers,
            'json'    => $body,
            'timeout' => 15,
        ];
        if (! $config->verifySSL) {
            $requestOptions['verify'] = false;
        }
        log_message('info', '[FinFlo] createSession: Calling FinFlo endpoint=' . $endpoint . ' amount_ugx=' . ($body['amount']) . ' verifySSL=' . ($config->verifySSL ? 'true' : 'false'));
        $response = $client->request('POST', $endpoint, $requestOptions);

        $statusCode = $response->getStatusCode();
        $bodyStr    = $response->getBody();
        $data       = json_decode($bodyStr, true);
        log_message('info', '[FinFlo] createSession: FinFlo response status=' . $statusCode . ' body=' . $bodyStr);

        if ($statusCode !== 200 || ! is_array($data) || ($data['status'] ?? '') !== 'success') {
            cache()->delete(self::PENDING_PREFIX . $pendingId);
            $message = is_array($data) ? ($data['message'] ?? 'Failed to create session') : 'Failed to create session';
            log_message('error', '[FinFlo] createSession: FinFlo error status=' . $statusCode . ' message=' . $message);
            return $this->error($message, $statusCode >= 400 ? $statusCode : 502);
        }

        $payUrl = $data['data']['pay_url'] ?? null;
        if (empty($payUrl)) {
            cache()->delete(self::PENDING_PREFIX . $pendingId);
            log_message('error', '[FinFlo] createSession: FinFlo response missing pay_url');
            return $this->error('Invalid response from payment provider', 502);
        }

        log_message('info', '[FinFlo] createSession: success pay_url length=' . strlen($payUrl) . ' pending_id=' . $pendingId);
        return $this->success([
            'pay_url'    => $payUrl,
            'pending_id' => $pendingId,
        ]);
    }

    /**
     * GET api/payments/finflo/status/:pending_id
     * Returns { "status": "pending"|"completed"|"expired" [, "tx_ref": "..." ] }. Auth required.
     */
    public function status(string $pendingId): ResponseInterface
    {
        $user = JwtAuth::user();
        if (! $user) {
            return $this->error('Unauthorized', 401);
        }

        $pendingId = trim($pendingId);
        if ($pendingId === '') {
            return $this->error('pending_id is required', 400);
        }

        $done = cache()->get(self::DONE_PREFIX . $pendingId);
        if (is_array($done)) {
            log_message('info', '[FinFlo] status: pending=' . $pendingId . ' -> completed');
            return $this->success([
                'status'  => 'completed',
                'tx_ref'  => $done['tx_ref'] ?? null,
            ]);
        }

        $pending = cache()->get(self::PENDING_PREFIX . $pendingId);
        if (is_array($pending) && (int) ($pending['user_id'] ?? 0) === (int) $user->id) {
            return $this->success(['status' => 'pending']);
        }

        log_message('info', '[FinFlo] status: pending=' . $pendingId . ' -> expired or not found');
        return $this->success(['status' => 'expired']);
    }

    /**
     * GET api/payments/finflo/callback?pending=xxx
     * FinFlo redirects the user's browser here on success with tx_ref, status, amount.
     * Public (no auth). Marks session completed and redirects to success page.
     */
    public function callback(): ResponseInterface
    {
        $pendingId = $this->request->getGet('pending');
        $txRef     = $this->request->getGet('tx_ref');
        $status    = $this->request->getGet('status');
        $amount    = $this->request->getGet('amount');
        log_message('info', '[FinFlo] callback: pending=' . $pendingId . ' status=' . $status . ' tx_ref=' . $txRef);

        if (empty($pendingId)) {
            return $this->redirectToResult('error', 'Invalid callback');
        }

        $pending = cache()->get(self::PENDING_PREFIX . $pendingId);
        if (! is_array($pending)) {
            return $this->redirectToResult('expired', 'Session expired');
        }

        if (($status ?? '') !== 'COMPLETED') {
            return $this->redirectToResult('error', 'Payment was not completed');
        }

        cache()->save(self::DONE_PREFIX . $pendingId, [
            'tx_ref' => $txRef,
            'status' => $status,
            'amount' => $amount,
        ], self::DONE_TTL);
        log_message('info', '[FinFlo] callback: marked completed for pending=' . $pendingId);

        return $this->redirectToResult('success', null, $pendingId);
    }

    /**
     * GET api/payments/finflo/cancel?pending=xxx
     * FinFlo redirects here when user cancels. Public. Show cancelled page.
     */
    public function cancel(): ResponseInterface
    {
        $pendingId = $this->request->getGet('pending');
        log_message('info', '[FinFlo] cancel: pending=' . $pendingId);
        return $this->redirectToResult('cancelled', 'Payment cancelled', $pendingId);
    }

    /**
     * GET api/payments/finflo/result?result=success|cancelled|error|expired&message=...&pending=...
     * Displays a simple HTML page after redirect from FinFlo (callback/cancel). Public.
     */
    public function result(): ResponseInterface
    {
        $result = $this->request->getGet('result') ?? 'error';
        $message = $this->request->getGet('message') ?? '';
        $pending = $this->request->getGet('pending') ?? '';

        $title   = 'Payment';
        $body    = 'You can close this page and return to the app.';
        $isError = false;

        switch ($result) {
            case 'success':
                $title = 'Payment successful';
                $body  = 'Payment was successful. You can close this window and return to the app.';
                break;
            case 'cancelled':
                $title = 'Payment cancelled';
                $body  = 'You cancelled the payment. You can close this window and return to the app.';
                break;
            case 'expired':
                $title   = 'Session expired';
                $body    = 'This payment session has expired. Please try again from the app.';
                $isError = true;
                break;
            default:
                $title   = 'Payment error';
                $body    = $message !== '' ? $message : 'Something went wrong. Please try again from the app.';
                $isError = true;
        }

        $html = '<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width,initial-scale=1"><title>' . htmlspecialchars($title) . '</title></head><body style="font-family:sans-serif;max-width:360px;margin:2rem auto;padding:1rem;text-align:center;"><h1>' . htmlspecialchars($title) . '</h1><p>' . htmlspecialchars($body) . '</p></body></html>';

        return $this->response->setBody($html)->setHeader('Content-Type', 'text/html');
    }

    private function redirectToResult(string $result, ?string $message, ?string $pendingId = null): ResponseInterface
    {
        helper('url');
        $url = base_url('api/payments/finflo/result') . '?' . http_build_query(array_filter([
            'result'   => $result,
            'message'  => $message,
            'pending'  => $pendingId,
        ]));
        return redirect()->to($url);
    }
}
