<?php

namespace App\Services;

use App\Models\Customer;
use App\Models\Tenant;
use App\Models\CommunicationUnitTransaction;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Throwable;

class SmsService
{
    public function __construct(
        private readonly PlatformSettings $settings,
        private readonly CommunicationUnitService $units,
    ) {
    }

    public function send(string $phone, string $message, ?Tenant $tenant = null, ?Customer $customer = null, array $context = []): array
    {
        $reservation = $this->reserveUnits($tenant, $customer, $message, $context);

        if (! $reservation['successful']) {
            return [
                'status' => 'failed',
                'message' => $reservation['message'],
                'units' => $reservation['units'],
            ];
        }

        $settings = $this->settings->smsSettings($tenant);
        $driver = $settings['driver'];
        $url = $settings['url'];

        if ($driver === 'ebulksms') {
            return $this->sendViaEbulkSms($phone, $message, $settings, $reservation);
        }

        if ($driver === 'log' || blank($url)) {
            Log::info('SMS notification logged.', [
                'tenant_id' => $tenant?->id,
                'to' => $phone,
                'message' => $message,
            ]);

            return [
                'status' => 'logged',
                'message' => 'SMS gateway is not configured, so the SMS was written to the log.',
                'units' => $reservation['units'],
            ];
        }

        try {
            $payload = [
                $settings['to_param'] => $phone,
                $settings['message_param'] => $message,
            ];

            if (filled($settings['sender'])) {
                $payload[$settings['sender_param']] = $settings['sender'];
            }

            $request = Http::timeout(20)->acceptJson();

            if (filled($settings['token'])) {
                $request = $request->withToken($settings['token']);
            }

            $response = strtoupper($settings['method']) === 'GET'
                ? $request->get($url, $payload)
                : $request->post($url, $payload);

            if ($response->successful()) {
                return ['status' => 'sent', 'message' => 'SMS sent.', 'units' => $reservation['units']];
            }

            $this->refundUnits($reservation['transaction'], 'SMS gateway returned HTTP '.$response->status().'.');

            return [
                'status' => 'failed',
                'message' => 'SMS gateway returned HTTP '.$response->status().'.',
                'units' => $reservation['units'],
            ];
        } catch (Throwable $exception) {
            report($exception);
            $this->refundUnits($reservation['transaction'], $exception->getMessage());

            return [
                'status' => 'failed',
                'message' => $exception->getMessage(),
                'units' => $reservation['units'],
            ];
        }
    }

    /**
     * @return array{successful: bool, message: string, units: int, transaction: CommunicationUnitTransaction|null}
     */
    private function reserveUnits(?Tenant $tenant, ?Customer $customer, string $message, array $context): array
    {
        if (! $tenant) {
            return ['successful' => true, 'message' => 'Platform message.', 'units' => 0, 'transaction' => null];
        }

        $context['message_length'] = function_exists('mb_strlen') ? mb_strlen($message) : strlen($message);

        return $this->units->reserveForSend($tenant, CommunicationUnitService::CHANNEL_SMS, $message, $customer, $context);
    }

    private function refundUnits(?CommunicationUnitTransaction $transaction, string $reason): void
    {
        if ($transaction) {
            $this->units->refundUsage($transaction, $reason);
        }
    }

    private function sendViaEbulkSms(string $phone, string $message, array $settings, array $reservation): array
    {
        $url = filled($settings['url'] ?? null)
            ? $settings['url']
            : 'https://api.ebulksms.com/sendsms';
        $username = $settings['username'] ?? null;
        $apiKey = $settings['token'] ?? null;
        $sender = $settings['sender'] ?? null;

        if (blank($username) || blank($apiKey) || blank($sender)) {
            $this->refundUnits($reservation['transaction'], 'eBulkSMS username, API key, or sender name is missing.');

            return [
                'status' => 'failed',
                'message' => 'eBulkSMS username, API key, and sender name are required.',
                'units' => $reservation['units'],
            ];
        }

        try {
            $response = Http::timeout(20)
                ->accept('*/*')
                ->get($url, [
                    'username' => $username,
                    'apikey' => $apiKey,
                    'sender' => $sender,
                    'messagetext' => $message,
                    'flash' => '0',
                    'recipients' => $phone,
                    'dndsender' => (string) ($settings['dnd_sender'] ?? '1'),
                ]);

            $body = trim($response->body());
            $status = strtoupper(strtok($body, '|') ?: $body);

            if ($response->successful() && $status === 'SUCCESS') {
                return [
                    'status' => 'sent',
                    'message' => 'SMS sent through eBulkSMS.',
                    'units' => $reservation['units'],
                ];
            }

            $error = $body !== '' ? $body : 'eBulkSMS returned HTTP '.$response->status().'.';
            $this->refundUnits($reservation['transaction'], $error);

            return [
                'status' => 'failed',
                'message' => 'eBulkSMS failed: '.$error,
                'units' => $reservation['units'],
            ];
        } catch (Throwable $exception) {
            report($exception);
            $this->refundUnits($reservation['transaction'], $exception->getMessage());

            return [
                'status' => 'failed',
                'message' => $exception->getMessage(),
                'units' => $reservation['units'],
            ];
        }
    }
}
