<?php

use classes\subuser_contact_verification_service;
use objects\subusers_o;

function subuser_contact_verification_test_subuser(array $values = []): subusers_o
{
    $subuser = new subusers_o();
    $subuser->id = -1;
    $subuser->getObjectProperties();

    $defaults = [
        'name' => 'Test Driver',
        'email' => 'driver@example.com',
        'email_verified_at' => null,
        'phone_country_code' => 45,
        'phone' => 12345678,
        'phone_verified_at' => null,
    ];

    foreach ([...$defaults, ...$values] as $property => $value) {
        $subuser->{$property}->set($value);
    }

    return $subuser;
}

function subuser_contact_verification_test_redis(): object
{
    return new class {
        public array $values = [];
        public array $ttls = [];

        public function get(string $key): ?string
        {
            return $this->values[$key] ?? null;
        }

        public function setEx(string $key, string $value, int $expiration): self
        {
            $this->values[$key] = $value;
            $this->ttls[$key] = $expiration;
            return $this;
        }

        public function delete(string $key): self
        {
            unset($this->values[$key], $this->ttls[$key]);
            return $this;
        }
    };
}

it('sends and verifies a subuser email verification code without storing plaintext codes', function (): void {
    $redis = subuser_contact_verification_test_redis();
    $sentEmails = [];
    $now = 1_700_000_000;
    $subuser = subuser_contact_verification_test_subuser();
    $service = new subuser_contact_verification_service(
        redis: $redis,
        codeGenerator: fn () => '123456',
        timeProvider: fn () => $now,
        emailSender: function (string $destination, string $code) use (&$sentEmails): void {
            $sentEmails[] = compact('destination', 'code');
        }
    );

    $delivery = $service->sendCode($subuser, 'email', ['type' => 'test', 'id' => 42]);

    expect($delivery['status'])->toBe('sent')
        ->and($delivery['expires_in'])->toBe(subuser_contact_verification_service::CODE_TTL_SECONDS)
        ->and($sentEmails)->toBe([
            [
                'destination' => 'driver@example.com',
                'code' => '123456',
            ],
        ])
        ->and($redis->values)->toHaveCount(1);

    $storedChallenge = json_decode((string)array_values($redis->values)[0], true);
    expect($storedChallenge['hash'])->not->toBe('123456')
        ->and($storedChallenge['attempts'])->toBe(0);

    $wrong = $service->verifyCode($subuser, 'email', '000000', ['type' => 'test', 'id' => 42]);
    expect($wrong['status'])->toBe('invalid_code');

    $updatedChallenge = json_decode((string)array_values($redis->values)[0], true);
    expect($updatedChallenge['attempts'])->toBe(1);

    $verified = $service->verifyCode($subuser, 'email', '123456', ['type' => 'test', 'id' => 42]);
    expect($verified['status'])->toBe('verified')
        ->and($subuser->email_verified_at->value())->toBe(date('Y-m-d H:i:s', $now))
        ->and($redis->values)->toBe([]);

    $status = $service->status($subuser);
    expect($status['email']['verified'])->toBeTrue()
        ->and($status['phone']['verified'])->toBeFalse()
        ->and($status['state'])->toBe('partial');
});

it('clears verification timestamps when subuser contact values change', function (): void {
    $subuser = subuser_contact_verification_test_subuser([
        'email_verified_at' => '2026-07-13 10:00:00',
        'phone_verified_at' => '2026-07-13 10:00:00',
    ]);
    $service = new subuser_contact_verification_service(redis: subuser_contact_verification_test_redis());

    $emailUpdate = $service->clearVerificationForChangedContacts($subuser, [
        'email' => 'new-driver@example.com',
        'phone' => 12345678,
    ]);
    expect($emailUpdate['email_verified_at'])->toBeNull()
        ->and($emailUpdate)->not->toHaveKey('phone_verified_at');

    $phoneUpdate = $service->clearVerificationForChangedContacts($subuser, [
        'phone_country_code' => 46,
    ]);
    expect($phoneUpdate['phone_verified_at'])->toBeNull()
        ->and($phoneUpdate)->not->toHaveKey('email_verified_at');
});

it('lets superusers set and clear contact verification state', function (): void {
    $now = 1_800_000_000;
    $subuser = subuser_contact_verification_test_subuser();
    $service = new subuser_contact_verification_service(
        redis: subuser_contact_verification_test_redis(),
        timeProvider: fn () => $now
    );

    $verified = $service->setVerificationState($subuser, 'email', true, ['type' => 'user', 'id' => 99]);
    expect($verified['status'])->toBe('verified')
        ->and($subuser->email_verified_at->value())->toBe(date('Y-m-d H:i:s', $now));

    $unverified = $service->setVerificationState($subuser, 'email', false, ['type' => 'user', 'id' => 99]);
    expect($unverified['status'])->toBe('unverified')
        ->and($subuser->email_verified_at->value())->toBeNull();
});

it('refuses to mark missing contact values as verified', function (): void {
    $subuser = subuser_contact_verification_test_subuser([
        'email' => null,
    ]);
    $service = new subuser_contact_verification_service(redis: subuser_contact_verification_test_redis());

    $result = $service->setVerificationState($subuser, 'email', true, ['type' => 'user', 'id' => 99]);
    expect($result['status'])->toBe('missing_destination')
        ->and($subuser->email_verified_at->value())->toBeNull();
});
