<?php

use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Modules\Identity\Enums\TokenAbility;
use Modules\Identity\Mail\RegistrationCodeMail;
use Modules\Identity\Models\RegistrationVerification;

beforeEach(function () {
    config([
        'services.sms.enabled' => true,
        'services.sms.sms_poh.server' => 'https://sms.example.test/send',
        'services.sms.sms_poh.token' => 'test-token',
        'services.sms.sms_poh.sender' => 'App',
    ]);
});

test('requesting a code for a new email sends a mail and creates a pending verification', function () {
    Mail::fake();

    $this->postJson('/api/v1/auth/registration/request-code', ['identifier' => 'new@example.com'])
        ->assertSuccessful();

    Mail::assertSent(RegistrationCodeMail::class);

    $verification = RegistrationVerification::where('identifier', 'new@example.com')->sole();
    expect($verification->type)->toBe('email')
        ->and($verification->verified_at)->toBeNull();
});

test('requesting a code for a new phone number sends an sms', function () {
    Http::fake(['sms.example.test/*' => Http::response('OK', 200)]);

    $this->postJson('/api/v1/auth/registration/request-code', ['identifier' => '+959123456789'])
        ->assertSuccessful();

    Http::assertSent(fn ($request) => $request->url() === 'https://sms.example.test/send'
        && $request['to'] === '+959123456789');

    $verification = RegistrationVerification::where('identifier', '+959123456789')->sole();
    expect($verification->type)->toBe('phone');
});

test('requesting a code rejects an already registered email', function () {
    Mail::fake();
    User::factory()->create(['email' => 'taken@example.com']);

    $this->postJson('/api/v1/auth/registration/request-code', ['identifier' => 'taken@example.com'])
        ->assertUnprocessable()
        ->assertJsonValidationErrors('identifier');

    Mail::assertNothingSent();
});

test('requesting a code rejects an already registered phone', function () {
    User::factory()->create(['phone' => '+959123456789']);

    $this->postJson('/api/v1/auth/registration/request-code', ['identifier' => '+959123456789'])
        ->assertUnprocessable()
        ->assertJsonValidationErrors('identifier');
});

test('verifying with the correct code returns a verification token', function () {
    Mail::fake();
    $this->postJson('/api/v1/auth/registration/request-code', ['identifier' => 'new@example.com']);
    $verification = RegistrationVerification::where('identifier', 'new@example.com')->sole();

    // The plaintext code isn't returned by the API by design, so reach
    // into the model the same way the real code was generated to recover
    // it for the test — simplest is to reissue with a known code via the
    // factory instead of parsing outbound mail content.
    $verification->forceFill(['code' => Hash::make('654321')])->save();

    $this->postJson('/api/v1/auth/registration/verify-code', [
        'identifier' => 'new@example.com',
        'code' => '654321',
    ])
        ->assertSuccessful()
        ->assertJsonStructure(['verification_token']);

    expect($verification->fresh()->verified_at)->not->toBeNull();
});

test('verifying with the wrong code fails and increments attempts', function () {
    $verification = RegistrationVerification::factory()->create();

    $this->postJson('/api/v1/auth/registration/verify-code', [
        'identifier' => $verification->identifier,
        'code' => '000000',
    ])->assertUnprocessable();

    expect($verification->fresh()->attempts)->toBe(1);
});

test('verifying locks out after too many wrong attempts', function () {
    $verification = RegistrationVerification::factory()->create(['attempts' => 5]);

    $this->postJson('/api/v1/auth/registration/verify-code', [
        'identifier' => $verification->identifier,
        'code' => '000000',
    ])->assertUnprocessable();
});

test('verifying an expired code fails', function () {
    $verification = RegistrationVerification::factory()->expired()->create();

    $this->postJson('/api/v1/auth/registration/verify-code', [
        'identifier' => $verification->identifier,
        'code' => '000000',
    ])->assertUnprocessable();
});

test('registering with a valid verification token creates a user and returns a token', function () {
    $verification = RegistrationVerification::factory()->verified()->create(['identifier' => 'new@example.com']);

    $response = $this->postJson('/api/v1/auth/register', [
        'verification_token' => $verification->verification_token,
        'name' => 'Jane Doe',
        'password' => 'super-secret-password',
        'password_confirmation' => 'super-secret-password',
        'device_name' => 'iphone',
    ]);

    $response->assertSuccessful()->assertJsonStructure(['token']);

    $user = User::where('email', 'new@example.com')->sole();
    expect($user->name)->toBe('Jane Doe')
        ->and($verification->fresh()->consumed_at)->not->toBeNull();

    $accessToken = $user->tokens()->sole();
    expect($accessToken->abilities)->toEqualCanonicalizing(TokenAbility::customerAbilities());
});

test('registering fails when the verification token was already consumed', function () {
    $verification = RegistrationVerification::factory()->verified()->create([
        'identifier' => 'new@example.com',
        'consumed_at' => now(),
    ]);

    $this->postJson('/api/v1/auth/register', [
        'verification_token' => $verification->verification_token,
        'name' => 'Jane Doe',
        'password' => 'super-secret-password',
        'password_confirmation' => 'super-secret-password',
        'device_name' => 'iphone',
    ])->assertUnprocessable();
});

test('registering fails with an unknown verification token', function () {
    $this->postJson('/api/v1/auth/register', [
        'verification_token' => 'not-a-real-token',
        'name' => 'Jane Doe',
        'password' => 'super-secret-password',
        'password_confirmation' => 'super-secret-password',
        'device_name' => 'iphone',
    ])->assertUnprocessable();
});

test('the full request-code, verify-code, register flow works end to end', function () {
    Mail::fake();

    $this->postJson('/api/v1/auth/registration/request-code', ['identifier' => 'flow@example.com'])
        ->assertSuccessful();

    $verification = RegistrationVerification::where('identifier', 'flow@example.com')->sole();
    $verification->forceFill(['code' => Hash::make('111222')])->save();

    $verifyResponse = $this->postJson('/api/v1/auth/registration/verify-code', [
        'identifier' => 'flow@example.com',
        'code' => '111222',
    ])->assertSuccessful();

    $registerResponse = $this->postJson('/api/v1/auth/register', [
        'verification_token' => $verifyResponse->json('verification_token'),
        'name' => 'Flow User',
        'password' => 'super-secret-password',
        'password_confirmation' => 'super-secret-password',
        'device_name' => 'iphone',
    ])->assertSuccessful();

    $token = $registerResponse->json('token');

    $this->withHeader('Authorization', "Bearer {$token}")
        ->getJson('/api/v1/companies')
        ->assertSuccessful();
});
