<?php

namespace App\Tests\Sms;

use App\Sms\Entity\SmsLog;
use App\Sms\Message\SendSmsMessage;
use App\Sms\Provider\KavehNegarProvider;
use App\Sms\Provider\RanginehProvider;
use App\Sms\Repository\SmsLogRepository;
use App\Sms\Repository\SmsMessageTemplateRepository;
use App\Sms\Service\SmsService;
use App\Sms\Service\SmsTextResolver;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;

/**
 * سیاست lookup-only: هیچ مسیری نباید به sms/send.json خام (provider->send) سقوط کند.
 */
class SmsServiceLookupOnlyTest extends TestCase
{
    private KavehNegarProvider&MockObject $kavenegar;
    private SmsLogRepository&MockObject $logRepo;
    private MessageBusInterface&MockObject $bus;
    private SmsMessageTemplateRepository&MockObject $messageTemplateRepo;
    private SmsTextResolver&MockObject $textResolver;
    private SmsService $service;

    protected function setUp(): void
    {
        $this->kavenegar           = $this->createMock(KavehNegarProvider::class);
        $this->kavenegar->method('getName')->willReturn('kavenegar');
        $this->logRepo             = $this->createMock(SmsLogRepository::class);
        $this->bus                 = $this->createMock(MessageBusInterface::class);
        $this->messageTemplateRepo = $this->createMock(SmsMessageTemplateRepository::class);
        $this->textResolver        = $this->createMock(SmsTextResolver::class);

        $this->service = new SmsService(
            $this->kavenegar,
            $this->createMock(RanginehProvider::class),
            $this->logRepo,
            $this->bus,
            $this->messageTemplateRepo,
            $this->textResolver,
            $this->createMock(LoggerInterface::class),
        );
    }

    public function testSendNowWithoutTemplateCodeRefusesRawSendAndLogsFailure(): void
    {
        $this->kavenegar->expects($this->never())->method('send');
        $this->kavenegar->expects($this->never())->method('sendTemplate');

        $captured = null;
        $this->logRepo->expects($this->once())->method('save')
            ->willReturnCallback(function (SmsLog $log) use (&$captured) { $captured = $log; });

        $msg = new SendSmsMessage('09120000000', 'hello', 'kavenegar', tag: SmsLog::TAG_GLOBAL);
        $ok  = $this->service->sendNow($msg);

        $this->assertFalse($ok);
        $this->assertNotNull($captured);
        $this->assertFalse($captured->toArray()['success']);
    }

    public function testSendNowWithTemplateCodeUsesLookup(): void
    {
        $this->kavenegar->expects($this->never())->method('send');
        $this->kavenegar->expects($this->once())->method('sendTemplate')
            ->with('09120000000', 'clinicpro-otp', ['token' => '1234'])
            ->willReturn(true);

        $captured = null;
        $this->logRepo->expects($this->once())->method('save')
            ->willReturnCallback(function (SmsLog $log) use (&$captured) { $captured = $log; });

        $msg = new SendSmsMessage(
            '09120000000', 'code 1234', 'kavenegar',
            templateVars: ['token' => '1234'], templateCode: 'clinicpro-otp', tag: SmsLog::TAG_OTP,
        );

        $this->assertTrue($this->service->sendNow($msg));
        // template به‌کاررفته باید در SmsLog ثبت شود تا در پنل ادمین دیده شود.
        $this->assertSame('clinicpro-otp', $captured->toArray()['template_code']);
    }

    public function testDispatchTemplateForTagWithoutTemplateDoesNotEnqueueAndLogsFailure(): void
    {
        // TAG_GLOBAL در DEFAULTS نیست و در DB هم چیزی نداریم → باید رد شود، نه ارسال خام.
        $this->messageTemplateRepo->method('findByTag')->willReturn(null);
        $this->textResolver->method('resolve')->willReturn('any body');
        $this->bus->expects($this->never())->method('dispatch');
        $this->logRepo->expects($this->once())->method('save');

        $this->service->dispatchTemplate(SmsLog::TAG_GLOBAL, '09120000000', ['x' => 'y']);
    }

    public function testDispatchTemplateForKnownTagEnqueuesWithTemplateCode(): void
    {
        // TAG_OTP در DEFAULTS الگو + token_map دارد → باید با templateCode به صف برود.
        $this->messageTemplateRepo->method('findByTag')->willReturn(null);
        $this->textResolver->method('resolve')->willReturn('کد شما: 1234');

        $captured = null;
        $this->bus->expects($this->once())->method('dispatch')
            ->willReturnCallback(function (SendSmsMessage $m) use (&$captured) {
                $captured = $m;
                return new Envelope($m);
            });
        $this->logRepo->expects($this->never())->method('save');

        $this->service->dispatchTemplate(SmsLog::TAG_OTP, '09120000000', ['code' => '1234', 'site' => 'یزد']);

        $this->assertNotNull($captured);
        $this->assertSame('clinicpro-otp', $captured->templateCode);
        $this->assertArrayHasKey('token', $captured->templateVars);
        $this->assertSame('1234', $captured->templateVars['token']);
    }
}
