<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class LipilaPaymentService
{
    protected $baseUrl;
    protected $apiKey;

    public function __construct()
    {
        // Configure from environment
        $this->baseUrl = config('services.lipila.base_url', 'http://api.lipila.dev/');
        $this->apiKey = config('services.lipila.api_key', 'lsk_019affd5-8944-77be-a856-5469045017c0');
        
        Log::info('Lipila Service Initialized', [
            'base_url' => $this->baseUrl,
            'api_key_first_chars' => substr($this->apiKey, 0, 15) . '...',
            'environment' => config('app.env')
        ]);
    }

    /**
     * Initiate a mobile money collection (pay-in/customer payment)
     */
    public function collect(array $data, $companyId = null, $userId = null)
    {
        try {
            Log::info('=== LIPILA COLLECTION REQUEST START ===');
            Log::info('Request Data:', $data);
            Log::info('Context:', [
                'company_id' => $companyId ?? auth()->user()->companyid ?? null,
                'user_id' => $userId ?? auth()->id() ?? null
            ]);
            
            // Validate required fields
            if (!isset($data['account_number']) || empty($data['account_number'])) {
                throw new \Exception('Mobile number (account_number) is required for collection');
            }
            
            if (!isset($data['amount']) || $data['amount'] <= 0) {
                throw new \Exception('Valid amount is required for collection');
            }

            // Generate a unique reference ID
            $referenceId = 'COLL-' . Str::upper(Str::random(8)) . '-' . time();
            
            // Format mobile number to 260XXXXXXXXX format
            $formattedMobile = $this->formatMobileNumberForLipila($data['account_number']);
            
            Log::info('Mobile Number Processing:', [
                'input' => $data['account_number'],
                'formatted' => $formattedMobile,
                'reference_id' => $referenceId
            ]);
            
            // Prepare the payload according to Lipila documentation for collections
            $payload = [
                'referenceId' => $referenceId,
                'amount' => floatval($data['amount']),
                'accountNumber' => $formattedMobile,
                'currency' => $data['currency'] ?? 'ZMW',
                'narration' => $data['narration'] ?? 'Payment', // Single word as required
                'paymentMethod' => strtoupper($data['paymentmethod'] ?? ''),
            ];
            
            // Remove empty paymentMethod if not provided
            if (empty($payload['paymentMethod'])) {
                unset($payload['paymentMethod']);
            }
            
            // The endpoint from documentation - collections endpoint
            $endpoint = $this->baseUrl . 'api/v1/collections/mobile-money';
            
            // Prepare headers
            $headers = [
                'accept' => 'application/json',
                'Content-Type' => 'application/json',
                'x-api-key' => $this->apiKey,
            ];
            
            // Add callbackUrl header only if explicitly provided (optional)
            if (isset($data['callback_url']) && !empty($data['callback_url'])) {
                $headers['callbackUrl'] = $data['callback_url'];
                Log::info('Callback URL included in headers');
            }
            
            Log::info('API Request Details:', [
                'endpoint' => $endpoint,
                'payload' => $payload,
                'headers_keys' => array_keys($headers)
            ]);

            // Make API request
            $startTime = microtime(true);
            
            $response = Http::withHeaders($headers)
                ->timeout(60)
                ->retry(2, 100)
                ->post($endpoint, $payload);
                
            $responseTime = round((microtime(true) - $startTime) * 1000, 2);

            $statusCode = $response->status();
            $responseBody = $response->json();
            $rawResponse = $response->body();
            
            Log::info('=== LIPILA API RESPONSE ===');
            Log::info('Response Time:', ['ms' => $responseTime]);
            Log::info('Status Code:', ['code' => $statusCode]);
            Log::info('Response Body:', $responseBody);
            Log::info('Raw Response (first 500 chars):', ['raw' => substr($rawResponse, 0, 500)]);
            
            // Handle HTTP status codes
            $this->handleHttpResponse($statusCode, $responseBody);

            Log::info('=== LIPILA COLLECTION REQUEST END ===');

            // Return response wrapper
            return new class($response, $referenceId, $statusCode, $formattedMobile, $payload) {
                private $response;
                private $referenceId;
                private $statusCode;
                private $formattedMobile;
                private $payload;
                
                public function __construct($response, $referenceId, $statusCode, $formattedMobile, $payload)
                {
                    $this->response = $response;
                    $this->referenceId = $referenceId;
                    $this->statusCode = $statusCode;
                    $this->formattedMobile = $formattedMobile;
                    $this->payload = $payload;
                }
                
                public function successful()
                {
                    return $this->statusCode >= 200 && $this->statusCode < 300;
                }
                
                public function status()
                {
                    return $this->statusCode;
                }
                
                public function json()
                {
                    $data = $this->response->json();
                    if (is_array($data)) {
                        // Ensure all required fields are included
                        $data['referenceId'] = $data['referenceId'] ?? $this->referenceId;
                        $data['identifier'] = $data['identifier'] ?? $data['referenceId'] ?? $this->referenceId;
                        $data['status'] = $data['status'] ?? ($this->successful() ? 'PENDING' : 'FAILED');
                        $data['amount'] = $data['amount'] ?? $this->payload['amount'] ?? 0;
                        $data['accountNumber'] = $data['accountNumber'] ?? $this->formattedMobile;
                        $data['message'] = $data['message'] ?? ($this->successful() ? 'Collection initiated' : 'Collection failed');
                        $data['ipAddress'] = $data['ipAddress'] ?? request()->ip();
                    }
                    return $data;
                }
                
                public function getReferenceId()
                {
                    return $this->referenceId;
                }
                
                public function getBody()
                {
                    return $this->response->body();
                }
                
                public function getFormattedMobile()
                {
                    return $this->formattedMobile;
                }
                
                public function getPayload()
                {
                    return $this->payload;
                }
            };

        } catch (\Exception $e) {
            Log::error('=== LIPILA COLLECTION EXCEPTION ===');
            Log::error('Exception Details:', [
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine()
            ]);
            Log::error('Request Data at time of exception:', $data);
            Log::error('=== LIPILA COLLECTION EXCEPTION END ===');
            
            throw new \Exception('Lipila collection failed: ' . $e->getMessage());
        }
    }

    /**
     * Initiate a mobile money disbursement (payout)
     */
    public function disburse(array $data, $companyId = null, $userId = null)
    {
        try {
            Log::info('=== LIPILA DISBURSEMENT REQUEST START ===');
            Log::info('Request Data:', $data);
            Log::info('Context:', [
                'company_id' => $companyId ?? auth()->user()->companyid ?? null,
                'user_id' => $userId ?? auth()->id() ?? null
            ]);
            
            // Validate required fields
            if (!isset($data['account_number']) || empty($data['account_number'])) {
                throw new \Exception('Mobile number (account_number) is required for disbursement');
            }
            
            if (!isset($data['amount']) || $data['amount'] <= 0) {
                throw new \Exception('Valid amount is required for disbursement');
            }

            // Generate a unique reference ID
            $referenceId = 'DISB-' . Str::upper(Str::random(8)) . '-' . time();
            
            // Format mobile number to 260XXXXXXXXX format
            $formattedMobile = $this->formatMobileNumberForLipila($data['account_number']);
            
            Log::info('Mobile Number Processing:', [
                'input' => $data['account_number'],
                'formatted' => $formattedMobile,
                'reference_id' => $referenceId
            ]);
            
            // Prepare the payload according to Lipila documentation
            $payload = [
                'referenceId' => $referenceId,
                'amount' => floatval($data['amount']),
                'accountNumber' => $formattedMobile,
                'currency' => $data['currency'] ?? 'ZMW',
                'narration' => $data['narration'] ?? 'Disbursement', // Single word as required
            ];
            
            // The endpoint from documentation
            $endpoint = $this->baseUrl . 'api/v1/disbursements/mobile-money';
            
            // Prepare headers
            $headers = [
                'accept' => 'application/json',
                'Content-Type' => 'application/json',
                'x-api-key' => $this->apiKey,
            ];
            
            // Add callbackUrl header only if explicitly provided (optional)
            if (isset($data['callback_url']) && !empty($data['callback_url'])) {
                $headers['callbackUrl'] = $data['callback_url'];
                Log::info('Callback URL included in headers');
            }
            
            Log::info('API Request Details:', [
                'endpoint' => $endpoint,
                'payload' => $payload,
                'headers_keys' => array_keys($headers)
            ]);

            // Make API request
            $startTime = microtime(true);
            
            $response = Http::withHeaders($headers)
                ->timeout(60)
                ->retry(2, 100)
                ->post($endpoint, $payload);
                
            $responseTime = round((microtime(true) - $startTime) * 1000, 2);

            $statusCode = $response->status();
            $responseBody = $response->json();
            $rawResponse = $response->body();
            
            Log::info('=== LIPILA API RESPONSE ===');
            Log::info('Response Time:', ['ms' => $responseTime]);
            Log::info('Status Code:', ['code' => $statusCode]);
            Log::info('Response Body:', $responseBody);
            Log::info('Raw Response (first 500 chars):', ['raw' => substr($rawResponse, 0, 500)]);
            
            // Handle HTTP status codes
            $this->handleHttpResponse($statusCode, $responseBody);

            Log::info('=== LIPILA DISBURSEMENT REQUEST END ===');

            // Return response wrapper
            return new class($response, $referenceId, $statusCode, $formattedMobile, $payload) {
                private $response;
                private $referenceId;
                private $statusCode;
                private $formattedMobile;
                private $payload;
                
                public function __construct($response, $referenceId, $statusCode, $formattedMobile, $payload)
                {
                    $this->response = $response;
                    $this->referenceId = $referenceId;
                    $this->statusCode = $statusCode;
                    $this->formattedMobile = $formattedMobile;
                    $this->payload = $payload;
                }
                
                public function successful()
                {
                    return $this->statusCode >= 200 && $this->statusCode < 300;
                }
                
                public function status()
                {
                    return $this->statusCode;
                }
                
                public function json()
                {
                    $data = $this->response->json();
                    if (is_array($data)) {
                        // Ensure all required fields are included
                        $data['referenceId'] = $data['referenceId'] ?? $this->referenceId;
                        $data['identifier'] = $data['identifier'] ?? $data['referenceId'] ?? $this->referenceId;
                        $data['status'] = $data['status'] ?? ($this->successful() ? 'PENDING' : 'FAILED');
                        $data['amount'] = $data['amount'] ?? $this->payload['amount'] ?? 0;
                        $data['accountNumber'] = $data['accountNumber'] ?? $this->formattedMobile;
                        $data['message'] = $data['message'] ?? ($this->successful() ? 'Disbursement initiated' : 'Disbursement failed');
                        $data['ipAddress'] = $data['ipAddress'] ?? request()->ip();
                        $data['narration'] = $data['narration'] ?? $this->payload['narration'] ?? '';
                    }
                    return $data;
                }
                
                public function getReferenceId()
                {
                    return $this->referenceId;
                }
                
                public function getBody()
                {
                    return $this->response->body();
                }
                
                public function getFormattedMobile()
                {
                    return $this->formattedMobile;
                }
                
                public function getPayload()
                {
                    return $this->payload;
                }
            };

        } catch (\Exception $e) {
            Log::error('=== LIPILA DISBURSEMENT EXCEPTION ===');
            Log::error('Exception Details:', [
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine()
            ]);
            Log::error('Request Data at time of exception:', $data);
            Log::error('=== LIPILA DISBURSEMENT EXCEPTION END ===');
            
            throw new \Exception('Lipila disbursement failed: ' . $e->getMessage());
        }
    }
    
    /**
     * Format mobile number to 260XXXXXXXXX format for Lipila API
     */
    private function formatMobileNumberForLipila($number)
    {
        // Remove any non-digit characters (spaces, plus signs, dashes, etc.)
        $cleaned = preg_replace('/[^0-9]/', '', $number);
        
        Log::debug('Formatting mobile number - initial cleaning:', [
            'original' => $number,
            'cleaned' => $cleaned,
            'length' => strlen($cleaned)
        ]);
        
        // Handle Zambian mobile number formats
        $formatted = null;
        
        // Case 1: 10 digits starting with 0 (0976379025 -> 260976379025)
        if (strlen($cleaned) === 10 && str_starts_with($cleaned, '0')) {
            $formatted = '260' . substr($cleaned, 1);
            Log::info('Converted 0-prefixed 10-digit number:', [
                'from' => $cleaned,
                'to' => $formatted,
                'logic' => '260 + substr($cleaned, 1)'
            ]);
            return $formatted;
        }
        
        // Case 2: 9 digits (976379025 -> 260976379025)
        if (strlen($cleaned) === 9) {
            $formatted = '260' . $cleaned;
            Log::info('Converted 9-digit number:', [
                'from' => $cleaned,
                'to' => $formatted,
                'logic' => '260 + $cleaned'
            ]);
            return $formatted;
        }
        
        // Case 3: 12 digits starting with 260 (260976379025 -> keep as is)
        if (strlen($cleaned) === 12 && str_starts_with($cleaned, '260')) {
            Log::info('Number already in correct 260 format:', ['number' => $cleaned]);
            return $cleaned;
        }
        
        // Case 4: 12 digits starting with 0 and then 260 (0260976379025 -> 260976379025)
        if (strlen($cleaned) === 12 && str_starts_with($cleaned, '0') && substr($cleaned, 1, 3) === '260') {
            $formatted = substr($cleaned, 1); // Remove leading 0
            Log::info('Converted 0+260 prefixed number:', [
                'from' => $cleaned,
                'to' => $formatted,
                'logic' => 'substr($cleaned, 1)'
            ]);
            return $formatted;
        }
        
        // Case 5: Contains 260 somewhere but not at start
        if (str_contains($cleaned, '260')) {
            // Extract everything from 260 onwards
            $pos = strpos($cleaned, '260');
            $formatted = substr($cleaned, $pos);
            Log::warning('Extracted 260 from middle of number:', [
                'from' => $cleaned,
                'to' => $formatted,
                'position' => $pos
            ]);
            return $formatted;
        }
        
        // Fallback: Try to create a valid format
        Log::warning('Unusual mobile number format, attempting fallback formatting:', [
            'original' => $number,
            'cleaned' => $cleaned,
            'length' => strlen($cleaned)
        ]);
        
        // Remove any leading zeros
        $withoutLeadingZeros = ltrim($cleaned, '0');
        
        // If after removing zeros we have 9 digits, add 260
        if (strlen($withoutLeadingZeros) === 9) {
            $formatted = '260' . $withoutLeadingZeros;
            Log::info('Fallback: Created 260 format from 9 digits:', [
                'from' => $cleaned,
                'to' => $formatted,
                'logic' => '260 + ltrim($cleaned, "0")'
            ]);
            return $formatted;
        }
        
        // Last resort: Return as-is but log error
        Log::error('Cannot format mobile number to valid 260 format:', [
            'original' => $number,
            'cleaned' => $cleaned,
            'length' => strlen($cleaned),
            'final_output' => $cleaned
        ]);
        
        return $cleaned; // Return as-is as last resort
    }
    
    /**
     * Handle HTTP response status codes
     */
    private function handleHttpResponse($statusCode, $responseBody)
    {
        switch ($statusCode) {
            case 200:
            case 201:
                Log::info('✅ Success: Request processed successfully');
                break;
                
            case 400:
                $message = is_array($responseBody) ? 
                    json_encode($responseBody) : 
                    $responseBody;
                throw new \Exception('Bad Request: ' . $message);
                
            case 401:
                throw new \Exception('Unauthorized: Invalid or missing API key');
                
            case 403:
                throw new \Exception('Forbidden: API key does not have permission');
                
            case 404:
                throw new \Exception('Not Found: API endpoint does not exist');
                
            case 422:
                $errors = is_array($responseBody) && isset($responseBody['errors']) ? 
                    implode(', ', $responseBody['errors']) : 
                    json_encode($responseBody);
                throw new \Exception('Validation Error: ' . $errors);
                
            case 429:
                throw new \Exception('Rate Limit Exceeded: Too many requests');
                
            case 500:
                throw new \Exception('Lipila API Server Error: Internal server error');
                
            case 502:
            case 503:
            case 504:
                throw new \Exception('Service Unavailable: Lipila API may be down - Status: ' . $statusCode);
                
            default:
                if ($statusCode >= 400) {
                    $message = is_array($responseBody) && isset($responseBody['message']) ? 
                        $responseBody['message'] : 
                        json_encode($responseBody);
                    throw new \Exception('API Request Failed with status ' . $statusCode . ' - ' . $message);
                }
                break;
        }
    }
    
    /**
     * Check payment status (works for both collections and disbursements)
     */
    public function checkPaymentStatus($referenceId, $type = 'collection')
    {
        try {
            Log::info('Checking Lipila payment status', [
                'referenceId' => $referenceId,
                'type' => $type
            ]);
            
            // Determine endpoint based on type
            $endpoint = $type === 'disbursement' 
                ? 'api/v1/disbursements/' . $referenceId
                : 'api/v1/collections/' . $referenceId;
            
            $response = Http::withHeaders([
                'accept' => 'application/json',
                'x-api-key' => $this->apiKey,
            ])->timeout(30)
              ->get($this->baseUrl . $endpoint);

            $statusCode = $response->status();
            $responseBody = $response->json();

            Log::info('Lipila Status Check Response:', [
                'referenceId' => $referenceId,
                'type' => $type,
                'status' => $statusCode,
                'body' => $responseBody
            ]);

            if ($statusCode >= 400) {
                throw new \Exception('Status check failed with status ' . $statusCode);
            }

            return $responseBody;
        } catch (\Exception $e) {
            Log::error('Lipila Status Check Error: ' . $e->getMessage(), [
                'referenceId' => $referenceId,
                'type' => $type
            ]);
            throw new \Exception('Failed to check payment status: ' . $e->getMessage());
        }
    }
    
    /**
     * Test API connectivity
     */
    public function testConnection()
    {
        try {
            Log::info('Testing Lipila API connection');
            
            $response = Http::withHeaders([
                'accept' => 'application/json',
                'x-api-key' => $this->apiKey,
            ])->timeout(10)
              ->get(rtrim($this->baseUrl, '/'));
              
            return [
                'connected' => $response->successful(),
                'status' => $response->status(),
                'url' => rtrim($this->baseUrl, '/')
            ];
        } catch (\Exception $e) {
            Log::error('Lipila Connection Test Error:', ['error' => $e->getMessage()]);
            return [
                'connected' => false,
                'error' => $e->getMessage(),
                'url' => rtrim($this->baseUrl, '/')
            ];
        }
    }
    
    /**
     * Test mobile number formatting (for debugging)
     */
    public function testMobileNumberFormatting($testNumber = '0976379025')
    {
        $testNumbers = [
            '0976379025',           // Common format
            '976379025',            // Without leading 0
            '260976379025',         // Already correct
            '+260976379025',        // With plus
            '0260976379025',        // With leading 0 and 260
            '0976 379 025',         // With spaces
            '0976-379-025',         // With dashes
            $testNumber,            // Parameter
        ];
        
        $results = [];
        foreach ($testNumbers as $number) {
            $results[$number] = $this->formatMobileNumberForLipila($number);
        }
        
        Log::info('=== MOBILE NUMBER FORMATTING TEST ===', $results);
        return $results;
    }
    
    /**
     * Test collection with a specific mobile number
     */
    public function testCollectionWithNumber($mobileNumber)
    {
        try {
            Log::info('=== TESTING LIPILA COLLECTION ===', ['mobile_number' => $mobileNumber]);
            
            $result = $this->collect([
                'amount' => 1.00,
                'account_number' => $mobileNumber,
                'currency' => 'ZMW',
                'paymentmethod' => 'MTN',
            ], 1, 1);
            
            Log::info('Test collection result:', [
                'successful' => $result->successful(),
                'status' => $result->status(),
                'reference_id' => $result->getReferenceId(),
                'formatted_mobile' => $result->getFormattedMobile()
            ]);
            
            return $result;
        } catch (\Exception $e) {
            Log::error('Test collection failed:', [
                'mobile_number' => $mobileNumber,
                'error' => $e->getMessage()
            ]);
            throw $e;
        }
    }
    
    /**
     * Test disbursement with a specific mobile number
     */
    public function testDisbursementWithNumber($mobileNumber)
    {
        try {
            Log::info('=== TESTING LIPILA DISBURSEMENT ===', ['mobile_number' => $mobileNumber]);
            
            $result = $this->disburse([
                'amount' => 1.00,
                'account_number' => $mobileNumber,
                'currency' => 'ZMW',
            ], 1, 1);
            
            Log::info('Test disbursement result:', [
                'successful' => $result->successful(),
                'status' => $result->status(),
                'reference_id' => $result->getReferenceId(),
                'formatted_mobile' => $result->getFormattedMobile()
            ]);
            
            return $result;
        } catch (\Exception $e) {
            Log::error('Test disbursement failed:', [
                'mobile_number' => $mobileNumber,
                'error' => $e->getMessage()
            ]);
            throw $e;
        }
    }
}