<?php

namespace App\Services;

use App\Models\GeneralSetting;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class SmsService
{
    private string $apiUrl = 'https://bulksmsbd.net/api/smsapi';
    private string $balanceApiUrl = 'https://bulksmsbd.net/api/getBalanceApi';
    private string $apiKey;
    private string $senderId;
    private ?string $lastError = null;

    public function __construct()
    {
        $settings = GeneralSetting::getContent();

        $this->apiKey = (string) ($settings['sms']['bulksmsbd_api_key'] ?? config('services.bulksmsbd.api_key', ''));
        $this->senderId = (string) ($settings['sms']['bulksmsbd_sender_id'] ?? config('services.bulksmsbd.sender_id', 'GPS'));
    }

    public function send(string $phone, string $message): bool
    {
        $this->lastError = null;

        if (app()->environment('local') && empty($this->apiKey)) {
            Log::info("[SmsService] DEMO mode - To: {$phone} | Msg: {$message}");
            return true;
        }

        try {
            $providerPhone = ltrim(trim($phone), '+');
            $response = Http::asForm()->post($this->apiUrl, [
                'api_key' => $this->apiKey,
                'type' => 'text',
                'number' => $providerPhone,
                'senderid' => $this->senderId,
                'message' => $message,
            ]);

            $status = $response->status();
            $rawBody = trim((string) $response->body());
            $body = $this->decodeResponseBody($rawBody);

            Log::info('[SmsService] Response', [
                'status' => $status,
                'phone' => $providerPhone,
                'json' => $body,
                'raw' => $rawBody,
            ]);

            $accepted = isset($body['response_code']) && (int) $body['response_code'] === 202;
            if (! $accepted) {
                $this->lastError = $body['error_message']
                    ?? $body['message']
                    ?? ($rawBody !== '' ? $rawBody : "Provider rejected request (HTTP {$status}).");

                Log::warning('[SmsService] Provider rejected', [
                    'status' => $status,
                    'error' => $this->lastError,
                ]);
            }

            return $accepted;
        } catch (\Throwable $e) {
            $this->lastError = $e->getMessage();
            Log::error('[SmsService] Failed: ' . $this->lastError);
            return false;
        }
    }

    public function sendOtp(string $phone, string $otp): bool
    {
        $message = "Your GPS Migration verification code is: {$otp}. Valid for 5 minutes. Do not share this code.";
        return $this->send($phone, $message);
    }

    public function getBalance(): array
    {
        $this->lastError = null;

        if (empty($this->apiKey)) {
            return [
                'success' => false,
                'balance' => null,
                'message' => 'BulkSMSBD API key is not configured.',
                'raw' => null,
            ];
        }

        try {
            $response = Http::asForm()->timeout(20)->post($this->balanceApiUrl, [
                'api_key' => $this->apiKey,
            ]);

            $status = $response->status();
            $rawBody = trim((string) $response->body());
            $decoded = $this->decodeResponseBody($rawBody);
            $balance = $this->extractBalance($decoded, $rawBody);

            if ($response->successful() && $balance !== null) {
                return [
                    'success' => true,
                    'balance' => $balance,
                    'message' => 'Current SMS balance fetched successfully.',
                    'raw' => $rawBody,
                ];
            }

            $message = $this->providerMessage($decoded, $rawBody, $status);
            $this->lastError = $message;

            Log::warning('[SmsService] Balance lookup failed', [
                'status' => $status,
                'raw' => $rawBody,
                'decoded' => $decoded,
                'error' => $message,
            ]);

            return [
                'success' => false,
                'balance' => null,
                'message' => $message,
                'raw' => $rawBody,
            ];
        } catch (\Throwable $e) {
            $this->lastError = $e->getMessage();
            Log::error('[SmsService] Balance lookup exception: ' . $this->lastError);

            return [
                'success' => false,
                'balance' => null,
                'message' => $this->lastError,
                'raw' => null,
            ];
        }
    }

    public static function generateOtp(): string
    {
        return str_pad((string) random_int(1000, 9999), 4, '0', STR_PAD_LEFT);
    }

    public function getLastError(): ?string
    {
        return $this->lastError;
    }

    private function decodeResponseBody(string $rawBody): array|string|null
    {
        if ($rawBody === '') {
            return null;
        }

        $decoded = json_decode($rawBody, true);

        return json_last_error() === JSON_ERROR_NONE ? $decoded : $rawBody;
    }

    private function providerMessage(array|string|null $decoded, string $rawBody, int $status): string
    {
        if (is_array($decoded)) {
            return (string) ($decoded['error_message'] ?? $decoded['message'] ?? "Provider rejected request (HTTP {$status}).");
        }

        return $rawBody !== '' ? $rawBody : "Provider rejected request (HTTP {$status}).";
    }

    private function extractBalance(array|string|null $decoded, string $rawBody): ?string
    {
        if (is_array($decoded)) {
            foreach (['balance', 'credit', 'credits', 'current_balance'] as $key) {
                if (isset($decoded[$key]) && $decoded[$key] !== '') {
                    return trim((string) $decoded[$key]);
                }
            }
        }

        if ($rawBody === '') {
            return null;
        }

        if (preg_match('/([0-9]+(?:\.[0-9]+)?)/', $rawBody, $matches)) {
            return $matches[1];
        }

        return null;
    }
}
