<?php

namespace Tests\Feature;

use App\Src\Encryption;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Mtc\Modules\Members\Classes\Auth;
use Mtc\Modules\Members\Classes\Authenticator;
use Mtc\Modules\Members\Models\Member;
use Tests\TestCase;

/**
 * Login resolves the account via the deterministic blind index, not the
 * encrypted email column (Authenticator::login(), Authenticator.php:54):
 *
 *     Member::where('email_hash', Encryption::makeHash($email))->firstOrFail();
 *
 * These tests pin the behaviour that keeps that hash correct so members stay
 * able to log in.
 *
 * Requires the docker DB:
 *     make shell
 *     ./vendor/bin/phpunit --filter=MemberEmailHashLoginTest
 */
class MemberEmailHashLoginTest extends TestCase
{
    private const PASSWORD = 'Secret123!';

    /** @var int[] Member ids created during the test, removed in tearDown. */
    private array $createdMemberIds = [];

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

        // The Auth facade resolves 'members_auth', which is only bound during a
        // web request (modules/Members/hooks.php, gated on MEMBERS_SECTION).
        App::bind('members_auth', fn () => new Authenticator());
    }

    public function test_new_member_can_log_in_with_correct_credentials(): void
    {
        $email = $this->uniqueEmail();
        $this->createLoginableMember($email);

        $this->assertTrue(Auth::login($email, self::PASSWORD));
    }

    public function test_creating_a_member_derives_email_hash_from_their_email(): void
    {
        $email  = $this->uniqueEmail();
        $member = $this->createLoginableMember($email);

        $this->assertSame(Encryption::makeHash($email), $this->emailHashOf($member));
    }

    public function test_email_hash_survives_a_save_that_follows_a_partial_select(): void
    {
        $email  = $this->uniqueEmail();
        $member = $this->createLoginableMember($email);

        $partial = Member::select('id', 'failed_login_count')->find($member->id);
        $partial->failed_login_count = 0;
        $partial->save();

        $this->assertSame(Encryption::makeHash($email), $this->emailHashOf($member));
    }

    public function test_member_can_still_log_in_after_a_save_that_follows_a_partial_select(): void
    {
        $email  = $this->uniqueEmail();
        $member = $this->createLoginableMember($email);

        $partial = Member::select('id', 'failed_login_count')->find($member->id);
        $partial->failed_login_count = 0;
        $partial->save();

        $this->assertTrue(Auth::login($email, self::PASSWORD));
    }

    private function createLoginableMember(string $email): Member
    {
        $member = new Member();
        $member->email              = $email;
        $member->password           = Auth::hashPassword(self::PASSWORD);
        $member->first_login        = date('Y-m-d H:i:s');
        $member->failed_login_count = 0;
        $member->save();

        $this->createdMemberIds[] = $member->id;

        return $member;
    }

    private function emailHashOf(Member $member): ?string
    {
        return DB::table('members')->where('id', $member->id)->value('email_hash');
    }

    private function uniqueEmail(): string
    {
        return 'hashbug+' . uniqid() . '@example.com';
    }

    protected function tearDown(): void
    {
        if (!empty($this->createdMemberIds)) {
            DB::table('members')->whereIn('id', $this->createdMemberIds)->delete();
        }

        parent::tearDown();
    }
}
