<?php

namespace Gaco\SnapBi\Helpers;

use Gaco\SnapBi\Enums\ServiceCode;
use Gaco\SnapBi\SnapBi;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Log;

/**
 * SnapBi Helper Class
 * 
 * Main helper class for SNAP BI library
 * Provides instance management, response code utilities, and signature helpers
 */
class SnapBiHelper
{
    /**
     * Get SnapBi instance
     * 
     * @return SnapBi
     */
    public static function instance(): SnapBi
    {
        return new SnapBi();
    }

    /**
     * Ensure X-TIMESTAMP header exists in response
     * 
     * @param \Illuminate\Http\JsonResponse $response
     * @return \Illuminate\Http\JsonResponse
     */
    public static function ensureTimestampHeader(JsonResponse $response): JsonResponse
    {
        if (!$response->headers->has('X-TIMESTAMP')) {
            $timestamp = Date::now()->utc()->format('Y-m-d\TH:i:s.000\Z');
            $response->headers->set('X-TIMESTAMP', $timestamp);
        }

        return $response;
    }

    /**
     * Add response signature to JSON response
     * 
     * For Virtual Account endpoints (symmetric signature HMAC_SHA512)
     * 
     * Signature formula:
     * stringToSign = HTTPMethod + ":" + EndpointUrl + ":" + AccessToken + ":" + Lowercase(HexEncode(SHA256(minify(RequestBody)))) + ":" + TimeStamp
     * 
     * @param \Illuminate\Http\JsonResponse $response
     * @param string $clientSecret Base64 encoded client secret
     * @param string|null $httpMethod HTTP method (default: POST)
     * @param string|null $endpointUrl Endpoint URL (optional, can be extracted from request)
     * @param string|null $accessToken Access token (optional, can be extracted from Authorization header)
     * @return \Illuminate\Http\JsonResponse
     */
    public static function addResponseSignature(
        JsonResponse $response,
        string $clientSecret,
        ?string $httpMethod = 'POST',
        ?string $endpointUrl = null,
        ?string $accessToken = null
    ): JsonResponse {
        // Ensure X-TIMESTAMP header exists
        $response = self::ensureTimestampHeader($response);

        $timestamp = $response->headers->get('X-TIMESTAMP');
        $responseBody = $response->getContent();

        // Minify response body (same as request body minification)
        // Request body = Response body (exact copy)
        $minifiedBody = json_encode(json_decode($responseBody, true), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        // Calculate body hash: Lowercase(HexEncode(SHA256(minify(RequestBody))))
        $bodyHash = strtolower(bin2hex(hash('sha256', $minifiedBody, true)));

        // Build signature string
        // Format: HTTPMethod + ":" + EndpointUrl + ":" + AccessToken + ":" + Lowercase(HexEncode(SHA256(minify(RequestBody)))) + ":" + TimeStamp
        $signatureString = sprintf(
            '%s:%s:%s:%s:%s',
            strtoupper($httpMethod ?? 'POST'),
            $endpointUrl ?? '',
            $accessToken ?? '',
            $bodyHash,
            $timestamp
        );

        // Decode client secret from Base64
        $clientSecretDecoded = base64_decode($clientSecret, true);
        if ($clientSecretDecoded === false) {
            return $response;
        }

        // Generate HMAC-SHA512 signature
        $signature = base64_encode(hash_hmac('sha512', $signatureString, $clientSecretDecoded, true));

        // Add X-SIGNATURE header
        $response->headers->set('X-SIGNATURE', $signature);

        return $response;
    }

    /**
     * Get default response message based on HTTP code and case code
     * 
     * @param int $httpCode HTTP status code
     * @param int $caseCode Case code
     * @return string Default response message
     */
    public static function getDefaultMessage(int $httpCode, int $caseCode = 0): string
    {
        return match ([$httpCode, $caseCode]) {
            // Success
            [200, 0] => 'Successful',

            // 400 Bad Request
            [400, 0] => 'Bad Request',
            [400, 1] => 'Invalid Field Format {field_name}',
            [400, 2] => 'Invalid Mandatory Field {field_name}',

            // 401 Unauthorized
            [401, 0] => 'Unauthorized [reason]',
            [401, 1] => 'Invalid Token (B2B)',
            [401, 2] => 'Invalid Customer Token',
            [401, 3] => 'Token Not Found (B2B)',
            [401, 4] => 'Customer Token Not Found',

            // 403 Forbidden
            [403, 0] => 'Transaction Expired',
            [403, 1] => 'Feature Not Allowed [reason]',
            [403, 2] => 'Exceeds Transaction Amount Limit',
            [403, 3] => 'Suspected Fraud',
            [403, 4] => 'Activity Count Limit Exceeded',
            [403, 5] => 'Do Not Honor',
            [403, 6] => 'Feature Not Allowed At This Time. [reason]',
            [403, 7] => 'Card Blocked',
            [403, 8] => 'Card Expired',
            [403, 9] => 'Dormant Account',
            [403, 10] => 'Need To Set Token Limit',
            [403, 11] => 'OTP Blocked',
            [403, 12] => 'OTP Lifetime Expired',
            [403, 13] => 'OTP Sent To Cardholder',
            [403, 14] => 'Insufficient Funds',
            [403, 15] => 'Transaction Not Permitted.[reason]',
            [403, 16] => 'Suspend Transaction',
            [403, 17] => 'Token Limit Exceeded',
            [403, 18] => 'Inactive Card/Account/Customer',
            [403, 19] => 'Merchant Blacklisted',
            [403, 20] => 'Merchant Limit Exceed',
            [403, 21] => 'Set Limit Not Allowed',
            [403, 22] => 'Token Limit Invalid',
            [403, 23] => 'Account Limit Exceed',

            // 404 Not Found
            [404, 0] => 'Invalid Transaction Status',
            [404, 1] => 'Transaction Not Found',
            [404, 2] => 'Invalid Routing',
            [404, 3] => 'Bank Not Supported By Switch',
            [404, 4] => 'Transaction Cancelled',
            [404, 5] => 'Merchant Is Not Registered For Card Registration Services',
            [404, 6] => 'Need To Request OTP',
            [404, 7] => 'Journey Not Found',
            [404, 8] => 'Invalid Merchant',
            [404, 9] => 'No Issuer',
            [404, 10] => 'Invalid API Transition',
            [404, 11] => 'Invalid Card/Account/Customer [info]/Virtual Account',
            [404, 12] => 'Invalid Bill/Virtual Account [reason]',
            [404, 13] => 'Invalid Amount',
            [404, 14] => 'Paid Bill',
            [404, 15] => 'Invalid OTP',
            [404, 16] => 'Partner Not Found',
            [404, 17] => 'Invalid Terminal',
            [404, 18] => 'Inconsistent Request',
            [404, 19] => 'Invalid Bill/Virtual Account (Expired)',

            // 405 Method Not Allowed
            [405, 0] => 'Requested Function Is Not Supported',
            [405, 1] => 'Requested Operation Is Not Allowed',

            // 409 Conflict
            [409, 0] => 'Conflict',
            [409, 1] => 'Duplicate partnerReferenceNo',

            // 429 Too Many Requests
            [429, 0] => 'Too Many Requests',

            // 500 Internal Server Error
            [500, 0] => 'General Error',
            [500, 1] => 'Internal Server Error',
            [500, 2] => 'External Server Error',

            // 504 Gateway Timeout
            [504, 0] => 'Timeout',

            default => 'Error occurred',
        };
    }

    /**
     * Get valid case codes for a given HTTP code
     * 
     * @param int $httpCode HTTP status code
     * @return array<int> Valid case codes
     */
    public static function getValidCaseCodes(int $httpCode): array
    {
        return match ($httpCode) {
            200 => [0],
            400 => [0, 1, 2],
            401 => [0, 1, 2, 3, 4],
            403 => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
            404 => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
            405 => [0, 1],
            409 => [0, 1],
            429 => [0],
            500 => [0, 1, 2],
            504 => [0],
            default => [],
        };
    }

    /**
     * Check if case code is valid for given HTTP code
     * 
     * @param int $httpCode HTTP status code
     * @param int $caseCode Case code
     * @return bool True if valid, false otherwise
     */
    public static function isValidCaseCode(int $httpCode, int $caseCode): bool
    {
        $validCaseCodes = self::getValidCaseCodes($httpCode);
        return in_array($caseCode, $validCaseCodes, true);
    }

    /**
     * Get service code from request based on route action or endpoint URL
     * 
     * This method determines the service code by:
     * 1. First trying to get from route action method (controller method name)
     * 2. Fallback to endpoint URL path pattern matching
     * 
     * @param Request $request HTTP request
     * @return string Service code value (e.g., '73', '24', '25', etc.)
     */
    public static function getServiceCodeFromRequest(Request $request): string
    {
        // Try to get from route action method first
        $route = $request->route();
        if ($route) {
            $actionMethod = $route->getActionMethod();

            // Map controller method to ServiceCode enum
            $methodToServiceCode = [
                'authB2B' => ServiceCode::TOKEN,
                'inquiry' => ServiceCode::INQUIRY,
                'payment' => ServiceCode::PAYMENT,
                'transferInquiry' => ServiceCode::TRANSFER_INQUIRY,
                'updateStatusVa' => ServiceCode::UPDATE_STATUS_VA,
                'inquiryStatus' => ServiceCode::INQUIRY_STATUS,
                'notifyPaymentIntrabank' => ServiceCode::NOTIF_PAYMENT_INTRABANK,
            ];

            if (isset($methodToServiceCode[$actionMethod])) {
                return $methodToServiceCode[$actionMethod]->value;
            }
        }

        // Fallback: determine from endpoint URL path
        $path = $request->path();

        // Remove version prefix if exists (e.g., api/v1.0/)
        $path = preg_replace('#^api/v\d+\.\d+/#', '', $path);

        // Remove bank parameter if exists (e.g., ocbc/, bca/, etc.)
        $path = preg_replace('#^(ocbc|bca|mandiri|cimb)/#', '', $path);

        // Map endpoint path to ServiceCode enum
        if (str_contains($path, 'access-token/b2b')) {
            return ServiceCode::TOKEN->value;
        } elseif (str_contains($path, 'transfer-va/inquiry')) {
            return ServiceCode::INQUIRY->value;
        } elseif (str_contains($path, 'transfer-va/payment')) {
            return ServiceCode::PAYMENT->value;
        } elseif (str_contains($path, 'transfer-va/transfer-inquiry')) {
            return ServiceCode::TRANSFER_INQUIRY->value;
        } elseif (str_contains($path, 'transfer-va/update-status')) {
            return ServiceCode::UPDATE_STATUS_VA->value;
        } elseif (str_contains($path, 'transfer-va/inquiry-status')) {
            return ServiceCode::INQUIRY_STATUS->value;
        } elseif (str_contains($path, 'transfer-va/notify-payment-intrabank')) {
            return ServiceCode::NOTIF_PAYMENT_INTRABANK->value;
        }

        // Default to INQUIRY if cannot determine
        if (class_exists(Log::class)) {
            Log::warning('Snap-BI: Cannot determine service code from request', [
                'path' => $request->path(),
                'route' => $route ? $route->getName() : null,
                'action' => $route ? $route->getActionMethod() : null,
            ]);
        }

        return ServiceCode::INQUIRY->value; // Default to INQUIRY
    }
}
