<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Carbon\Carbon;
use phpseclib\Crypt\Rijndael;
// Removed phpseclib3 - using native OpenSSL instead

class NIDAController extends Controller
{
    protected $baseUrl;
    protected $client;
    protected $userId;
    protected $clientNameOrIP;
    protected $nidaPublicKey;
    protected $clientPrivateKey;
    protected array $errorCodes = [
        '000' => 'Stakeholder Account is ok.',
        '001' => 'Stakeholder Account does not exist',
        '002' => 'Stakeholder Account is Suspended',
        '003' => 'Stakeholder Account has expired',
        '010' => 'Certificate is ok.',
        '011' => 'Stakeholder Certificates are not ok.',
        '012' => 'CIG Certificate is not ok.',
        '020' => 'Signature Verification Success',
        '021' => 'Signature Verification fail',
        '030' => 'Decryption of Key and IV is Success',
        '031' => 'Decryption of Key not Success',
        '032' => 'Decryption of IV not Success',
        '040' => 'Decryption of Payload Success',
        '041' => 'Decryption of Payload not Success',
        '042' => 'Payload is not in XML format',
        '050' => 'This CIG Method is Allowed for your institution',
        '051' => 'This CIG Method is not allowed for your institution',
        '060' => 'Phone Number entered is similar to number registered by NIDA',
        '061' => 'Phone Number entered is different from Number registered by NIDA',
        '062' => 'OTP has been successfully sent',
        '063' => 'Failed to send OTP',
        '064' => 'Failed to verify OTP',
        '070' => 'Payload Encrypted Successfully',
        '071' => 'Failed to Encrypt Payload',
        '080' => 'Signature Successfully generated',
        '081' => 'Failed to generate signature',
        '090' => 'Key and IV Encrypted Successfully',
        '091' => 'Failed to Encrypt Key',
        '092' => 'Failed to Encrypt IV',
        '100' => 'Response XML processed successfully',
        '101' => 'Failed to process XML Response',
        '102' => 'No data assigned for this Stakeholder Account',
        '111' => 'Unable to process XML Payload Request',
        '120' => 'Success Loaded Question Codes.',
        '121' => 'Failed to load Question Codes.',
        '122' => 'Limited number of attempts on answering security questions has been reached.',
        '123' => 'Previous answer was correct',
        '124' => 'Previous answer was incorrect',
        '130' => 'NIN is valid',
        '131' => 'NIN is invalid',
        '132' => 'NIN not Found',
        '140' => 'Biometric Fingerprint Verification Success',
        '141' => 'Biometric Fingerprint Verification Failed',
        '142' => 'Invalid Fingerprint data',
        '143' => 'Invalid Finger codes',
        '150' => 'Transaction has successfully verified',
        '151' => 'Transaction has not successfully verified',
        '152' => 'Transaction not found',
        '153' => 'Invalid web method',
        '154' => 'Invalid Transaction ID',
        '155' => 'Invalid Stakeholder ID',
        '156' => 'Missing Input Parameters',
        '160' => 'Identification Match Found',
        '161' => 'Identification Match not Found',
        '162' => 'Unspecified Response from Matching Engine',
        '163' => 'Wait for Matching Engine to process your request',
        '164' => 'Request ID not Registered',
        '165' => 'Payload is missing fingerprint',
        '170' => 'Alternative to biometric verification allowed for this ID holder',
        '171' => 'Alternative to biometric verification not allowed for this ID holder',
        '172' => 'Switch to alternative to biometric verification',
        '00'  => 'General Success',
        '01'  => 'General Failure.',
    ];
    public function __construct()
    {
        $this->baseUrl = config('services.nida.base_url', 'https://nacer01/TZ_CIG/GatewayService.svc');
        $this->userId = config('services.nida.user_id');
        $this->clientNameOrIP = config('services.nida.client_name_or_ip', request()->ip());


        $this->nidaPublicKey = config('services.nida.public_key');
        $this->clientPrivateKey = config('services.nida.private_key');

        $this->client = new Client([
            'timeout' => 30,
            'verify' => false,
        ]);
    }
    public function getPersonDetailsFromUrl($nin)
    {
        // Create a fake request with the NIN
        $request = new Request(['nin' => $nin]);
        return $this->getPersonDetails($request);
    }
    public function getChallengeResponseFromUrl($nin, $rqcode, $answ)
    {
        $request = new Request([
            'nin' => $nin,
            'rqcode' => $rqcode,
            'answer' => $answ,
        ]);
        return $this->getChallengeDetails($request);
    }
    public function getPersonDetails(Request $request)
    {
        // Validate the NIN input
        $validator = Validator::make($request->all(), [
            'nin' => 'required|string|size:20|regex:/^[0-9]{20}$/',
        ]);

        if ($validator->fails()) {
            return response()->json([
                'success' => false,
                'error' => 'Invalid NIN format. NIN must be exactly 20 digits.',
                'errors' => $validator->errors(),
            ], 400);
        }

        $nin = $request->input('nin');

        try {
            $soapPayload = $this->generatePayload($nin);
            $result = $this->makeNidaRequest($soapPayload);

            return response()->json($result);
        } catch (\Exception $e) {
            Log::error('NIDA API Error', [
                'nin' => $nin,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            return response()->json([
                'success' => false,
                'error' => 'Failed to retrieve person details from NIDA.',
                'message' => config('app.debug') ? $e->getMessage() : 'Internal server error',
            ], 500);
        }
    }
    public function getChallengeDetails(Request $request)
    {

        $validator = Validator::make($request->all(), [
            'nin'     => 'required|string|size:20|regex:/^[0-9]{20}$/',
            'rqcode'  => 'required|string|max:10',
            'answer'  => 'required|string|max:255',
        ]);

        if ($validator->fails()) {
            return response()->json([
                'success' => false,
                'error' => 'Validation failed.',
                'errors' => $validator->errors(),
            ], 400);
        }

        $nin     = $request->input('nin');
        $rqcode  = $request->input('rqcode');
        $answer  = $request->input('answer');


        try {
            $soapPayload = $this->generateChallengePayload($nin, $rqcode, $answer);
            $result = $this->makeNidaRequest($soapPayload);

            return response()->json($result);
        } catch (\Exception $e) {
            Log::error('NIDA API Error', [
                'nin' => $nin,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            return response()->json([
                'success' => false,
                'error' => 'Failed to retrieve person details from NIDA.',
                'message' => config('app.debug') ? $e->getMessage() : 'Internal server error',
            ], 500);
        }
    }
    private function generateChallengePayload($nin, $rqcode, $answer)
    {
        $key = random_bytes(32);
        $iv = random_bytes(32);

        $encryptedKey = $this->encryptRSA($key);
        $encryptedIV = $this->encryptRSA($iv);

        $payload = "<Payload><NIN>{$nin}</NIN>
        <RQCode>{$rqcode}</RQCode> 
        <QNANSW>{$answer}</QNANSW>
        </Payload>";
        $encryptedPayload = base64_encode($this->encryptByRijndael256($payload, $key, $iv));

        // Sign the encrypted payload (not the original)
        $signature = $this->signDigitally($this->encryptByRijndael256($payload, $key, $iv));

        return $this->generateSoapPayload([
            'EncryptedCryptoIV' => $encryptedIV,
            'EncryptedCryptoKey' => $encryptedKey,
            'Payload' => $encryptedPayload,
            'Signature' => $signature,
            'ClientNameOrIP' => $this->clientNameOrIP,
            'Id' => uniqid(),
            'TimeStamp' => date('Y-m-d H:i:s'),
            'UserId' => $this->userId
        ]);
    }
    private function generatePayload($nin)
    {
        $key = random_bytes(32);
        $iv = random_bytes(32);

        $encryptedKey = $this->encryptRSA($key);
        $encryptedIV = $this->encryptRSA($iv);

        $payload = "<Payload><NIN>{$nin}</NIN></Payload>";
        $encryptedPayload = base64_encode($this->encryptByRijndael256($payload, $key, $iv));

        // Sign the encrypted payload (not the original)
        $signature = $this->signDigitally($this->encryptByRijndael256($payload, $key, $iv));

        return $this->generateSoapPayload([
            'EncryptedCryptoIV' => $encryptedIV,
            'EncryptedCryptoKey' => $encryptedKey,
            'Payload' => $encryptedPayload,
            'Signature' => $signature,
            'ClientNameOrIP' => $this->clientNameOrIP,
            'Id' => uniqid(),
            'TimeStamp' => date('Y-m-d H:i:s'),
            'UserId' => $this->userId
        ]);
    }
    public function encryptRSA(string $data): string
    {
        $publicKey = openssl_pkey_get_public($this->nidaPublicKey);
        if (!$publicKey) {
            throw new \Exception("Invalid public key or certificate.");
        }
        $success = openssl_public_encrypt($data, $encrypted, $publicKey, OPENSSL_PKCS1_PADDING);
        if (!$success) {
            throw new \Exception("Encryption failed.");
        }
        return base64_encode($encrypted);
    }

    private function encryptByRijndael256(string $data, string $key, string $iv): string
    {
        $rijndael = new Rijndael('cbc');
        $rijndael->setBlockLength(256);
        $rijndael->setKey($key);
        $rijndael->setIV($iv);
        return $rijndael->encrypt($data);
    }


    private function signDigitally(string $data): string
    {
        // If no private key configured, return base64 encoded hash as fallback
        if (!$this->clientPrivateKey) {
            Log::warning('NIDA private key not configured, using fallback');
            return base64_encode(hash('sha1', $data, true));
        }

        $privateKey = openssl_pkey_get_private($this->clientPrivateKey);
        if (!$privateKey) {
            Log::warning('Invalid NIDA private key format, using fallback');
            return base64_encode(hash('sha1', $data, true));
        }

        $result = openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA1);
        openssl_free_key($privateKey);

        if (!$result) {
            Log::warning('Digital signing failed, using fallback');
            return base64_encode(hash('sha1', $data, true));
        }

        return base64_encode($signature);
    }


    private function generateSoapPayload(array $data): string
    {
        $doc = new \DOMDocument('1.0', 'utf-8');
        $doc->formatOutput = true;

        // Create Envelope with namespaces
        $envelope = $doc->createElementNS('http://www.w3.org/2003/05/soap-envelope', 'soap:Envelope');
        $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:nid', 'http://schemas.datacontract.org/2004/07/NID_API');
        $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:tem', 'http://tempuri.org/');
        $doc->appendChild($envelope);

        // Header
        $header = $doc->createElementNS('http://www.w3.org/2003/05/soap-envelope', 'soap:Header');
        $header->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:wsa', 'http://www.w3.org/2005/08/addressing');
        $envelope->appendChild($header);

        $action = $doc->createElement('wsa:Action');
        $action->appendChild($doc->createTextNode('http://tempuri.org/IGatewayService/RQVerification'));
        $to = $doc->createElement('wsa:To');
        $to->appendChild($doc->createTextNode($this->baseUrl));
        $header->appendChild($action);
        $header->appendChild($to);

        // Body
        $body = $doc->createElement('soap:Body');
        $envelope->appendChild($body);

        $rqVerification = $doc->createElement('tem:RQVerification');
        $body->appendChild($rqVerification);

        $iRequest = $doc->createElement('tem:iRequest');
        $rqVerification->appendChild($iRequest);

        // nid:Body
        $nidBody = $doc->createElement('nid:Body');
        $iRequest->appendChild($nidBody);

        $cryptoInfo = $doc->createElement('nid:CryptoInfo');
        $nidBody->appendChild($cryptoInfo);

        $encryptedCryptoIV = $doc->createElement('nid:EncryptedCryptoIV');
        $encryptedCryptoIV->appendChild($doc->createTextNode($data['EncryptedCryptoIV'] ?? ''));
        $encryptedCryptoKey = $doc->createElement('nid:EncryptedCryptoKey');
        $encryptedCryptoKey->appendChild($doc->createTextNode($data['EncryptedCryptoKey'] ?? ''));

        $cryptoInfo->appendChild($encryptedCryptoIV);
        $cryptoInfo->appendChild($encryptedCryptoKey);

        $payload = $doc->createElement('nid:Payload');
        $payload->appendChild($doc->createTextNode($data['Payload'] ?? ''));
        $signature = $doc->createElement('nid:Signature');
        $signature->appendChild($doc->createTextNode($data['Signature'] ?? ''));

        $nidBody->appendChild($payload);
        $nidBody->appendChild($signature);

        // nid:Header
        $nidHeader = $doc->createElement('nid:Header');
        $iRequest->appendChild($nidHeader);

        $clientNameOrIP = $doc->createElement('nid:ClientNameOrIP');
        $clientNameOrIP->appendChild($doc->createTextNode($data['ClientNameOrIP'] ?? ''));
        $id = $doc->createElement('nid:Id');
        $id->appendChild($doc->createTextNode($data['Id'] ?? ''));
        $timeStamp = $doc->createElement('nid:TimeStamp');
        $timeStamp->appendChild($doc->createTextNode($data['TimeStamp'] ?? ''));
        $userId = $doc->createElement('nid:UserId');
        $userId->appendChild($doc->createTextNode($data['UserId'] ?? ''));

        $nidHeader->appendChild($clientNameOrIP);
        $nidHeader->appendChild($id);
        $nidHeader->appendChild($timeStamp);
        $nidHeader->appendChild($userId);

        return $doc->saveXML();
    }
    protected function makeNidaRequest($soapXml)
    {
        Log::info('Making NIDA API request', [
            'url' => $this->baseUrl,
            'soap_xml_length' => strlen($soapXml)
        ]);
        file_put_contents(storage_path('logs/soap-request.xml'), $soapXml);
        $response = $this->client->post($this->baseUrl, [
            'headers' => [
                'Content-Type' => 'application/soap+xml; charset=utf-8',
                'SOAPAction' => '"http://tempuri.org/IGatewayService/RQVerification"',
                'User-Agent' => 'KOPA-NIDA-Client/1.0',
            ],
            'body' => $soapXml,
        ]);
        $responseBody = $response->getBody()->getContents();
        Log::info('NIDA API response received', [
            'status_code' => $response->getStatusCode(),
            'response_length' => strlen($responseBody)
        ]);

        return $this->parseNidaResponse($responseBody);
    }

    protected function parseNidaResponse($responseXml)
    {
        // Log the raw response for debugging
        Log::info('Raw NIDA API Response:', [
            'response' => $responseXml,
            'length' => strlen($responseXml),
            'first_100_chars' => substr($responseXml, 0, 100)
        ]);

        try {
            $xml = new \SimpleXMLElement($responseXml);
            $xml->registerXPathNamespace('s', 'http://www.w3.org/2003/05/soap-envelope');
            $xml->registerXPathNamespace('b', 'http://schemas.datacontract.org/2004/07/NID_API');

            $codeNodes = $xml->xpath('//b:Code');
            $code = isset($codeNodes[0]) ? (string) $codeNodes[0] : null;

            if ($code === "120") {
                $encryptedCryptoIV = (string) $xml->xpath('//b:EncryptedCryptoIV')[0];
                $encryptedCryptoKey = (string) $xml->xpath('//b:EncryptedCryptoKey')[0];
                $payload = (string) $xml->xpath('//b:Payload')[0];
                $signature = (string) $xml->xpath('//b:Signature')[0];

                if ($this->verifySignature($payload, $signature)) {
                    $KEY = $this->decryptRSA($encryptedCryptoKey);
                    $IV = $this->decryptRSA($encryptedCryptoIV);
                    $decrypted = $this->decryptByRijndael256(base64_decode($payload), $KEY, $IV);
                    $xml = new \SimpleXMLElement($decrypted);
                    $nin = (string) $xml->NIN;
                    $rqCode = (string) $xml->RQCode;
                    $previousAnsStatus = (string) $xml->PREV_ANSW_CODE;
                    $en = (string) $xml->EN;
                    $sw = (string) $xml->SW;
                    return [
                        'success' => true,
                        'data' => [
                            'NIN' => $nin,
                            'RQCode' => $rqCode,
                            'EN' => $en,
                            'SW' => $sw,
                            'PREV_ANSW_CODE' => $previousAnsStatus,
                            'CODE' => "120"
                        ],
                        'error' => $code,
                        'error_code' => $code
                    ];
                } else {
                    Log::info('Invalid signature during parsing', [
                        'code' => $code,
                        'error' => $this->errorCodes[$code] ?? 'Unknown error'
                    ]);

                    return [
                        'success' => false,
                        'error' => $this->errorCodes[$code] ?? 'Unknown error',
                        'error_code' => $code
                    ];
                }
            } else if ($code === "00") {
                $encryptedCryptoIV = (string) $xml->xpath('//b:EncryptedCryptoIV')[0];
                $encryptedCryptoKey = (string) $xml->xpath('//b:EncryptedCryptoKey')[0];
                $payload = (string) $xml->xpath('//b:Payload')[0];
                $signature = (string) $xml->xpath('//b:Signature')[0];

                if ($this->verifySignature($payload, $signature)) {
                    $KEY = $this->decryptRSA($encryptedCryptoKey);
                    $IV = $this->decryptRSA($encryptedCryptoIV);
                    $decrypted = $this->decryptByRijndael256(base64_decode($payload), $KEY, $IV);
                    $xml = new \SimpleXMLElement($decrypted);

                    return [
                        'success' => true,
                        'data' => [
                            'CODE' => "00",
                            'NIN' => (string) $xml->NIN,
                            'FIRSTNAME' => (string) $xml->FIRSTNAME,
                            'MIDDLENAME' => (string) $xml->MIDDLENAME,
                            'SURNAME' => (string) $xml->SURNAME,
                            'SEX' => (string) $xml->SEX,
                            'DATEOFBIRTH' => (string) $xml->DATEOFBIRTH,
                            'PHOTO' => (string) $xml->PHOTO,
                            'NATIONALITY' => (string) $xml->NATIONALITY
                        ],
                        'error' => $code,
                        'error_code' => $code
                    ];
                } else {
                    Log::info('Invalid signature during parsing', [
                        'code' => $code,
                        'error' => $this->errorCodes[$code] ?? 'Unknown error'
                    ]);

                    return [
                        'success' => true,
                        'data' => [
                            'CODE' => $code
                        ],
                        'error' => $this->errorCodes[$code] ?? 'Unknown error',
                        'error_code' => $code
                    ];
                }
            } else {
                Log::info('NIDA API response error', [
                    'code' => $code,
                    'error' => $this->errorCodes[$code] ?? 'Unknown error'
                ]);

                return [
                    'success' => true,
                    'data' => [
                        'CODE' => $code
                    ],
                    'error' => $this->errorCodes[$code] ?? 'Unknown error',
                    'error_code' => $code
                ];
            }
        } catch (\Exception $e) {
            Log::error('Exception while parsing NIDA response', [
                'error' => $e->getMessage()
            ]);

            return [
                'success' => false,
                'error' => 'Failed to parse XML: ' . $e->getMessage(),
                'error_code' => null
            ];
        }
    }
    public function verifySignature(string $payload, string $signature): bool
    {
        try {
            $publicKey = openssl_pkey_get_public($this->nidaPublicKey);
            if (!$publicKey) {
                throw new \Exception("Invalid public key.");
            }

            $result = openssl_verify(
                base64_decode($payload),          // Data
                base64_decode($signature),        // Signature
                $publicKey,                       // Public key
                OPENSSL_ALGO_SHA1                 // Hash algorithm
            );

            return $result === 1;
        } catch (\Throwable $e) {
            log_message('error', 'Signature verification failed: ' . $e->getMessage());
            return false;
        }
    }

    public function decryptRSA(string $encryptedBase64): string
    {
        $privateKey = openssl_pkey_get_private($this->clientPrivateKey);
        if (!$privateKey) {
            throw new \Exception("Invalid private key.");
        }

        $encrypted = base64_decode($encryptedBase64);
        $success = openssl_private_decrypt($encrypted, $decrypted, $privateKey, OPENSSL_PKCS1_PADDING);

        if (!$success) {
            throw new \Exception("Decryption failed.");
        }

        return $decrypted;
    }


    private function decryptByRijndael256(string $encryptedData, string $key, string $iv): string
    {
        $rijndael = new Rijndael('cbc');
        $rijndael->setBlockLength(256);
        $rijndael->setKey($key);
        $rijndael->setIV($iv);
        return $rijndael->decrypt($encryptedData);
    }
}
