<?php

declare(strict_types=1);

use App\Mail\EmailVerificationCodeMail;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Tests\Concerns\SharesSupplierPdo;

// Самозапись пишет tenants/users/email_verifications через BYPASSRLS pgsql_supplier
// (на публичном роуте нет tenant-GUC). SharesSupplierPdo шарит PDO под DatabaseTransactions.
uses(DatabaseTransactions::class, SharesSupplierPdo::class);

beforeEach(function () {
    config(['services.captcha.fake_passes' => true]);
});

function registerPayload(array $over = []): array
{
    return array_merge([
        'email' => 'newclient@example.ru',
        'password' => 'fresh-pass-123',
        'accept_offer' => true,
        'accept_pdn' => true,
        'captcha_token' => 'tok-123',
    ], $over);
}

test('register создаёт pending-тенанта + неактивного владельца + код, без входа', function () {
    $r = $this->postJson('/api/auth/register', registerPayload());

    $r->assertStatus(201);
    $r->assertJsonPath('status', 'pending_email_confirm');
    $r->assertJsonPath('email', 'newclient@example.ru');
    expect($r->json('_dev_plain_code'))->toMatch('/^\d{6}$/');

    $user = User::where('email', 'newclient@example.ru')->first();
    expect($user)->not->toBeNull();
    expect($user->is_active)->toBeFalse();

    $tenant = Tenant::find($user->tenant_id);
    expect($tenant->status)->toBe('pending_email_confirm');
    expect((float) $tenant->balance_rub)->toBe(0.0);

    // Вход НЕ выполнен.
    $this->getJson('/api/auth/me')->assertStatus(401);
});

test('register ставит письмо с кодом в очередь (не синхронно)', function () {
    Mail::fake();

    $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);

    Mail::assertQueued(EmailVerificationCodeMail::class);
});

test('register не падает (201), если отправка письма с кодом сбоит', function () {
    // Имитируем сбой доставки: dispatch письма бросает исключение.
    Mail::shouldReceive('to')->andThrow(new RuntimeException('smtp down'));

    $r = $this->postJson('/api/auth/register', registerPayload());

    // Регистрация успешна: запись кода уже создана, клиент может «отправить повторно».
    $r->assertStatus(201)->assertJsonPath('status', 'pending_email_confirm');
    expect($r->json('_dev_plain_code'))->toMatch('/^\d{6}$/');
    expect(User::where('email', 'newclient@example.ru')->where('is_active', false)->exists())->toBeTrue();
});

test('register пишет email_verifications через pgsql_supplier (BYPASSRLS)', function () {
    $connections = [];
    DB::listen(function ($q) use (&$connections) {
        if (str_contains($q->sql, 'email_verifications')) {
            $connections[] = $q->connectionName;
        }
    });

    $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);

    expect($connections)->not->toBeEmpty();
    expect(array_values(array_unique($connections)))->toBe(['pgsql_supplier']);
});

test('register отклоняет неверную капчу (422), аккаунт не создаётся', function () {
    config(['services.captcha.fake_passes' => false]);

    $this->postJson('/api/auth/register', registerPayload())
        ->assertStatus(422)
        ->assertJsonValidationErrors(['captcha_token']);

    expect(User::where('email', 'newclient@example.ru')->exists())->toBeFalse();
});

test('register требует accept_offer, accept_pdn, captcha_token', function () {
    $this->postJson('/api/auth/register', registerPayload(['accept_offer' => false]))
        ->assertStatus(422)->assertJsonValidationErrors(['accept_offer']);

    $this->postJson('/api/auth/register', registerPayload(['accept_pdn' => false]))
        ->assertStatus(422)->assertJsonValidationErrors(['accept_pdn']);

    $payload = registerPayload();
    unset($payload['captcha_token']);
    $this->postJson('/api/auth/register', $payload)
        ->assertStatus(422)->assertJsonValidationErrors(['captcha_token']);
});

test('confirm верным кодом активирует тенанта/владельца, баланс 300, выполняет вход', function () {
    $reg = $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
    $code = $reg->json('_dev_plain_code');

    $r = $this->postJson('/api/auth/confirm-email', [
        'email' => 'newclient@example.ru',
        'code' => $code,
    ]);

    $r->assertOk();
    $r->assertJsonPath('user.email', 'newclient@example.ru');
    $r->assertJsonPath('requires_2fa', false);

    $user = User::where('email', 'newclient@example.ru')->first();
    expect($user->is_active)->toBeTrue();

    $tenant = Tenant::find($user->tenant_id);
    expect($tenant->status)->toBe('active');
    expect((float) $tenant->balance_rub)->toBe(300.0);
});

test('confirm неверным кодом → 422 + failed_attempts++', function () {
    $reg = $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
    $real = $reg->json('_dev_plain_code');
    $wrong = $real === '000000' ? '111111' : '000000';

    $r = $this->postJson('/api/auth/confirm-email', [
        'email' => 'newclient@example.ru',
        'code' => $wrong,
    ]);

    $r->assertStatus(422);
    expect($r->json('attempts_remaining'))->toBe(4);
});

test('5 неверных кодов инвалидируют запись (даже верный код больше не проходит)', function () {
    $reg = $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
    $real = $reg->json('_dev_plain_code');
    $wrong = $real === '000000' ? '111111' : '000000';

    for ($i = 0; $i < 5; $i++) {
        $this->postJson('/api/auth/confirm-email', [
            'email' => 'newclient@example.ru',
            'code' => $wrong,
        ])->assertStatus(422);
    }

    $this->postJson('/api/auth/confirm-email', [
        'email' => 'newclient@example.ru',
        'code' => $real,
    ])->assertStatus(422)->assertJsonPath('reason', 'too_many_attempts');
});

test('протухший код отклоняется, resend выдаёт рабочий', function () {
    $reg = $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
    $user = User::where('email', 'newclient@example.ru')->first();

    DB::table('email_verifications')->where('user_id', $user->id)
        ->update(['expires_at' => now()->subMinutes(5)]);

    $this->postJson('/api/auth/confirm-email', [
        'email' => 'newclient@example.ru',
        'code' => $reg->json('_dev_plain_code'),
    ])->assertStatus(422)->assertJsonPath('reason', 'expired');

    $resend = $this->postJson('/api/auth/resend-code', ['email' => 'newclient@example.ru'])->assertOk();
    $newCode = $resend->json('_dev_plain_code');
    expect($newCode)->toMatch('/^\d{6}$/');

    $this->postJson('/api/auth/confirm-email', [
        'email' => 'newclient@example.ru',
        'code' => $newCode,
    ])->assertOk();
});

test('повторный register на неподтверждённый email не создаёт второго тенанта', function () {
    $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);
    $this->postJson('/api/auth/register', registerPayload())->assertStatus(201);

    expect(User::where('email', 'newclient@example.ru')->count())->toBe(1);
    expect(Tenant::where('contact_email', 'newclient@example.ru')->count())->toBe(1);
});

test('register на активный email → 422', function () {
    $tenant = Tenant::factory()->create();
    User::factory()->create([
        'tenant_id' => $tenant->id,
        'email' => 'active@example.ru',
        'is_active' => true,
    ]);

    $this->postJson('/api/auth/register', registerPayload(['email' => 'active@example.ru']))
        ->assertStatus(422)->assertJsonValidationErrors(['email']);
});

test('resend на несуществующий email → унифицированный ответ (anti-enumeration)', function () {
    $this->postJson('/api/auth/resend-code', ['email' => 'nobody@example.ru'])
        ->assertOk()
        ->assertJsonMissingPath('_dev_plain_code');
});
