<?php

use App\Models\UserModel;
use App\Services\ProfileService;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use CodeIgniter\Test\FeatureTestTrait;
use Config\Tieasy;
use Tests\Support\Auth\AuthTestTrait;

/**
 * @internal
 */
final class ProfileFlowTest extends CIUnitTestCase
{
    use DatabaseTestTrait;
    use FeatureTestTrait;
    use AuthTestTrait;

    protected $namespace = 'App';
    protected $refresh   = true;
    protected $migrate   = true;
    protected $seed      = '';

    /**
     * @param array<string, mixed> $user
     *
     * @return array<string, mixed>
     */
    private function authSession(array $user): array
    {
        return [
            'user_id'                      => (int) $user['id'],
            'email'                        => (string) $user['email'],
            'profile_onboarding_completed' => (int) $user['profile_onboarding_completed'],
        ];
    }

    public function testUnauthenticatedOnboardingRedirectsToSignIn(): void
    {
        $this->withSession([])->get('profile/onboarding')->assertRedirectTo('/sign-in');
        $this->withSession([])->post('profile/onboarding', [])->assertRedirectTo('/sign-in');
    }

    public function testIncompleteUserCanAccessOnboarding(): void
    {
        $user = $this->createUser('new-profile@example.com', 0);
        $page = $this->withSession($this->authSession($user))->get('profile/onboarding');
        $page->assertOK();
        $page->assertSee('Let us serve you better!');
        $page->assertSee('new-profile@example.com');
        $page->assertDontSee('name="email"');
        $page->assertSee('Skip');
    }

    public function testCompletedUserCannotReopenOnboarding(): void
    {
        $user = $this->createUser('done-profile@example.com', 1);
        $this->withSession($this->authSession($user))
            ->get('profile/onboarding')
            ->assertRedirectTo('/dashboard');
        $this->withSession($this->authSession($user))
            ->post('profile/onboarding', ['profile_name' => 'Nope'])
            ->assertRedirectTo('/dashboard');
        $this->dontSeeInDatabase('users', ['id' => $user['id'], 'profile_name' => 'Nope']);
    }

    public function testBlankOptionalProfileSavesAndRedirectsToDashboard(): void
    {
        $user   = $this->createUser('blank@example.com', 0);
        $result = $this->withSession($this->authSession($user))->post('profile/onboarding', [
            'profile_name' => '',
            'age_group'    => '',
            'gender'       => '',
            'occupation'   => '',
            'phone'        => '',
        ]);
        $result->assertRedirectTo('/dashboard');

        $saved = (new UserModel())->find($user['id']);
        $this->assertSame(1, (int) $saved['profile_onboarding_completed']);
        $this->assertNull($saved['profile_name']);
        $this->assertNull($saved['age_group']);
        $this->assertNull($saved['gender']);
        $this->assertNull($saved['occupation']);
        $this->assertNull($saved['phone']);
        $this->assertSame('blank@example.com', $saved['email']);
    }

    public function testSkipCompletesOnboardingWithoutProfileValues(): void
    {
        $user   = $this->createUser('skipper@example.com', 0);
        $result = $this->withSession($this->authSession($user))->post('profile/onboarding', [
            'skip'         => '1',
            'profile_name' => 'Should Not Save',
            'occupation'   => 'Spy',
            'phone'        => '+1 555 0100',
        ]);
        $result->assertRedirectTo('/dashboard');

        $saved = (new UserModel())->find($user['id']);
        $this->assertSame(1, (int) $saved['profile_onboarding_completed']);
        $this->assertNull($saved['profile_name']);
        $this->assertNull($saved['occupation']);
        $this->assertNull($saved['phone']);

        $this->withSession($this->authSession($saved) + ['profile_onboarding_completed' => 1])
            ->get('profile/onboarding')
            ->assertRedirectTo('/dashboard');
    }

    public function testProfileNameSavesAndWhitespaceOnlyIsRejected(): void
    {
        $user = $this->createUser('named@example.com', 0);
        $ok   = $this->withSession($this->authSession($user))->post('profile/onboarding', [
            'profile_name' => '  Ada L.  ',
        ]);
        $ok->assertRedirectTo('/dashboard');
        $this->seeInDatabase('users', [
            'id'                           => $user['id'],
            'profile_name'                 => 'Ada L.',
            'profile_onboarding_completed' => 1,
        ]);

        $other = $this->createUser('spaces@example.com', 0);
        $bad   = $this->withSession($this->authSession($other))->post('profile/onboarding', [
            'profile_name' => '    ',
        ]);
        $bad->assertOK();
        $bad->assertSee(ProfileService::MSG_NAME_BLANK);
        $this->seeInDatabase('users', [
            'id'                           => $other['id'],
            'profile_onboarding_completed' => 0,
        ]);
    }

    public function testProfileNameOverLengthRejected(): void
    {
        $user = $this->createUser('longname@example.com', 0);
        $page = $this->withSession($this->authSession($user))->post('profile/onboarding', [
            'profile_name' => str_repeat('n', 101),
        ]);
        $page->assertSee(ProfileService::MSG_NAME_LENGTH);
        $this->seeInDatabase('users', ['id' => $user['id'], 'profile_onboarding_completed' => 0]);
    }

    public function testValidAgeGroupsAndGendersSaveOverHttp(): void
    {
        $config = new Tieasy();
        foreach ($config->ageGroups as $i => $group) {
            $user = $this->createUser("age{$i}@example.com", 0);
            $this->withSession($this->authSession($user))
                ->post('profile/onboarding', ['age_group' => $group])
                ->assertRedirectTo('/dashboard');
            $this->seeInDatabase('users', ['id' => $user['id'], 'age_group' => $group, 'profile_onboarding_completed' => 1]);
        }

        foreach ($config->genders as $i => $gender) {
            $user = $this->createUser("gender{$i}@example.com", 0);
            $this->withSession($this->authSession($user))
                ->post('profile/onboarding', ['gender' => $gender])
                ->assertRedirectTo('/dashboard');
            $this->seeInDatabase('users', ['id' => $user['id'], 'gender' => $gender]);
        }
    }

    public function testInvalidAgeGroupAndGenderRejected(): void
    {
        $user = $this->createUser('bad-enum@example.com', 0);
        $page = $this->withSession($this->authSession($user))->post('profile/onboarding', [
            'age_group' => 'Under 16',
            'gender'    => 'Unknown',
        ]);
        $page->assertSee(ProfileService::MSG_AGE);
        $page->assertSee(ProfileService::MSG_GENDER);
        $this->seeInDatabase('users', ['id' => $user['id'], 'profile_onboarding_completed' => 0]);
    }

    public function testOccupationAndPhoneRulesOverHttp(): void
    {
        $user = $this->createUser('job@example.com', 0);
        $this->withSession($this->authSession($user))->post('profile/onboarding', [
            'occupation' => str_repeat('x', 101),
        ])->assertSee(ProfileService::MSG_OCC_LENGTH);

        $okUser = $this->createUser('phone@example.com', 0);
        $this->withSession($this->authSession($okUser))->post('profile/onboarding', [
            'occupation' => 'Software Engineer',
            'phone'      => '+91 (555) 555-0100',
        ])->assertRedirectTo('/dashboard');
        $this->seeInDatabase('users', [
            'id'         => $okUser['id'],
            'occupation' => 'Software Engineer',
            'phone'      => '+91 (555) 555-0100',
        ]);

        $badPhone = $this->createUser('badphone@example.com', 0);
        $this->withSession($this->authSession($badPhone))->post('profile/onboarding', [
            'phone' => 'not a number!!',
        ])->assertSee(ProfileService::MSG_PHONE_INVALID);

        $longPhone = $this->createUser('longphone@example.com', 0);
        $this->withSession($this->authSession($longPhone))->post('profile/onboarding', [
            'phone' => '123456789012345678901',
        ])->assertSee(ProfileService::MSG_PHONE_LENGTH);
    }

    public function testSubmittedEmailCannotChangeAccountEmail(): void
    {
        $user = $this->createUser('real@example.com', 0);
        $this->withSession($this->authSession($user))->post('profile/onboarding', [
            'email'        => 'attacker@evil.com',
            'user_id'      => '0',
            'profile_name' => 'Pat',
        ])->assertRedirectTo('/dashboard');

        $saved = (new UserModel())->find($user['id']);
        $this->assertSame('real@example.com', $saved['email']);
        $this->assertSame('Pat', $saved['profile_name']);
        $this->dontSeeInDatabase('users', ['email' => 'attacker@evil.com']);
    }

    public function testSettingsLoadsAndUpdatesEditableValuesOnly(): void
    {
        $user = $this->createUser('settings@example.com', 1);
        (new UserModel())->skipValidation(true)->update($user['id'], [
            'profile_name' => 'Old Name',
            'age_group'    => '25–34',
            'gender'       => 'Male',
            'occupation'   => 'Analyst',
            'phone'        => '+1 202 555 0100',
        ]);

        $page = $this->withSession($this->authSession($user))->get('settings');
        $page->assertOK();
        $page->assertSee('Profile Information');
        $page->assertSee('Old Name');
        $page->assertSee('settings@example.com');
        $page->assertSee('Profile Name');
        $page->assertSee('Age Group');
        $page->assertSee('Gender');
        $page->assertSee('Occupation');
        $page->assertSee('Phone');
        $page->assertSee('Email');
        $page->assertDontSee('name="email"');
        $page->assertDontSee('Compact');
        $page->assertDontSee('Quick Guide');
        $page->assertDontSee('Delete Account');
        $page->assertDontSee('move completed');
        $page->assertDontSee('reminder');
        $page->assertDontSee('theme preferences');
        $page->assertDontSee('Notification preferences');

        $this->withSession($this->authSession($user))->post('settings/profile', [
            'profile_name' => 'New Name',
            'age_group'    => '35–44',
            'gender'       => 'Non-binary',
            'occupation'   => 'Designer',
            'phone'        => '+44 20 7946 0958',
            'email'        => 'attacker@evil.com',
        ])->assertRedirectTo('/settings');

        $saved = (new UserModel())->find($user['id']);
        $this->assertSame('New Name', $saved['profile_name']);
        $this->assertSame('35–44', $saved['age_group']);
        $this->assertSame('Non-binary', $saved['gender']);
        $this->assertSame('Designer', $saved['occupation']);
        $this->assertSame('+44 20 7946 0958', $saved['phone']);
        $this->assertSame('settings@example.com', $saved['email']);
        $this->assertSame(1, (int) $saved['profile_onboarding_completed']);
    }

    public function testSettingsCannotModifyAnotherUsersProfile(): void
    {
        $alice = $this->createUser('alice@example.com', 1);
        $bob   = $this->createUser('bob@example.com', 1);
        (new UserModel())->skipValidation(true)->update($bob['id'], [
            'profile_name' => 'Bob',
        ]);

        $this->withSession($this->authSession($alice))->post('settings/profile', [
            'user_id'      => (string) $bob['id'],
            'email'        => 'bob@example.com',
            'profile_name' => 'Hacked Bob',
        ])->assertRedirectTo('/settings');

        $this->seeInDatabase('users', ['id' => $alice['id'], 'profile_name' => 'Hacked Bob', 'email' => 'alice@example.com']);
        $this->seeInDatabase('users', ['id' => $bob['id'], 'profile_name' => 'Bob', 'email' => 'bob@example.com']);
    }

    public function testValidationPreservesValidSubmittedValues(): void
    {
        $user = $this->createUser('preserve@example.com', 0);
        $page = $this->withSession($this->authSession($user))->post('profile/onboarding', [
            'profile_name' => 'Keep Me',
            'occupation'   => str_repeat('z', 101),
            'phone'        => '+1 555 0100',
        ]);
        $page->assertSee(ProfileService::MSG_OCC_LENGTH);
        $page->assertSee('Keep Me');
        $page->assertSee('+1 555 0100');
        $this->seeInDatabase('users', ['id' => $user['id'], 'profile_onboarding_completed' => 0]);
    }
}
