<?php

namespace Tests\Feature;

use App\Models\Coupon;
use App\Models\PhoneVerification;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;

class VerificationFlowTest extends TestCase
{
    use LazilyRefreshDatabase;

    private const PHONE = '+993 61 92 92 48';

    private const UNMASKED_PHONE = '61929248';

    protected function setUp(): void
    {
        parent::setUp();

        Http::fake([
            config('services.sms.url') => Http::response('ok', 200),
        ]);
    }

    public function test_index_page_loads_for_new_visitors(): void
    {
        $this->get(route('verification.index'))
            ->assertOk()
            ->assertViewIs('verification.index');
    }

    public function test_send_otp_starts_verification_flow(): void
    {
        $this->post(route('verification.send'), ['phone' => self::PHONE])
            ->assertRedirect(route('verification.verify.form'));

        $this->assertDatabaseHas(PhoneVerification::class, [
            'phone' => self::UNMASKED_PHONE,
            'verified' => false,
        ]);

        Http::assertSentCount(1);
    }

    public function test_send_otp_rejects_duplicate_phone(): void
    {
        Coupon::factory()->create(['phone' => self::UNMASKED_PHONE]);

        $this->from(route('verification.index'))
            ->post(route('verification.send'), ['phone' => self::PHONE])
            ->assertRedirect(route('verification.index'))
            ->assertSessionHasErrors('phone');
    }

    public function test_send_otp_rejects_invalid_phone_format(): void
    {
        $this->from(route('verification.index'))
            ->post(route('verification.send'), ['phone' => '+993 81 23 45 67'])
            ->assertRedirect(route('verification.index'))
            ->assertSessionHasErrors('phone');
    }

    public function test_verify_form_requires_session_phone(): void
    {
        $this->get(route('verification.verify.form'))
            ->assertRedirect(route('verification.index'));
    }

    public function test_full_verification_flow_issues_coupon(): void
    {
        $this->post(route('verification.send'), ['phone' => self::PHONE])
            ->assertRedirect(route('verification.verify.form'));

        $otp = $this->extractOtpFromLastSms();

        $this->get(route('verification.verify.form'))->assertOk();

        $this->post(route('verification.verify'), ['otp' => $otp])
            ->assertRedirect(route('verification.congratulations'))
            ->assertCookie('device_registered');

        $this->assertDatabaseHas(Coupon::class, [
            'phone' => self::UNMASKED_PHONE,
        ]);

        $this->get(route('verification.congratulations'))
            ->assertOk()
            ->assertViewHas('code');
    }

    public function test_verify_otp_rejects_wrong_code(): void
    {
        PhoneVerification::factory()
            ->withOtp('1234')
            ->create(['phone' => self::UNMASKED_PHONE]);

        $this->withSession(['verify_phone' => self::UNMASKED_PHONE])
            ->from(route('verification.verify.form'))
            ->post(route('verification.verify'), ['otp' => '9999'])
            ->assertRedirect(route('verification.verify.form'))
            ->assertSessionHasErrors('otp');
    }

    public function test_verify_otp_rejects_expired_code(): void
    {
        PhoneVerification::factory()
            ->withOtp('1234')
            ->expired()
            ->create(['phone' => self::UNMASKED_PHONE]);

        $this->withSession(['verify_phone' => self::UNMASKED_PHONE])
            ->from(route('verification.verify.form'))
            ->post(route('verification.verify'), ['otp' => '1234'])
            ->assertRedirect(route('verification.verify.form'))
            ->assertSessionHasErrors(['otp' => 'Kodyň möhleti gutardy.']);
    }

    public function test_verify_otp_requires_session_phone(): void
    {
        $this->post(route('verification.verify'), ['otp' => '1234'])
            ->assertRedirect(route('verification.index'));
    }

    public function test_resend_otp_sends_new_sms(): void
    {
        $this->withSession(['verify_phone' => self::UNMASKED_PHONE])
            ->from(route('verification.verify.form'))
            ->post(route('verification.resend'))
            ->assertRedirect(route('verification.verify.form'))
            ->assertSessionHas('status');

        Http::assertSentCount(1);
    }

    public function test_congratulations_recovers_coupon_from_device_cookie(): void
    {
        $coupon = Coupon::factory()->create(['phone' => self::UNMASKED_PHONE]);

        $this->withCookie('device_registered', encrypt(self::UNMASKED_PHONE))
            ->get(route('verification.congratulations'))
            ->assertOk()
            ->assertViewIs('verification.congratulations')
            ->assertSee($coupon->code);
    }

    public function test_congratulations_redirects_when_session_coupon_missing_from_db(): void
    {
        $this->withSession(['coupon_code' => '9_STALE'])
            ->get(route('verification.congratulations'))
            ->assertRedirect(route('verification.index'))
            ->assertSessionMissing('coupon_code');
    }

    public function test_send_otp_is_rate_limited(): void
    {
        for ($i = 0; $i < 3; $i++) {
            $this->post(route('verification.send'), ['phone' => self::PHONE]);
        }

        $this->post(route('verification.send'), ['phone' => self::PHONE])
            ->assertStatus(429);
    }

    private function extractOtpFromLastSms(): string
    {
        $recorded = Http::recorded()->all();

        $this->assertNotEmpty($recorded);

        [$request] = $recorded[array_key_last($recorded)];

        preg_match('/(\d{4})/', (string) $request->data()['code'], $matches);

        $this->assertNotEmpty($matches[1]);

        return $matches[1];
    }
}
