<?php

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

/**
 * @internal
 */
final class SupportFlowTest 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'],
        ];
    }

    /**
     * @param array<string, mixed> $fields
     *
     * @return array<string, mixed>
     */
    private function withProfile(array $user, array $fields): array
    {
        (new UserModel())->skipValidation(true)->update($user['id'], $fields);
        $saved = (new UserModel())->find($user['id']);
        $this->assertIsArray($saved);

        return $saved;
    }

    private function categoryId(string $title = 'Technical Issue'): int
    {
        $row = db_connect()->table('support_categories')->where('title', $title)->get()->getRowArray();
        $this->assertIsArray($row);

        return (int) $row['id'];
    }

    /**
     * @param array<string, string> $override
     *
     * @return array<string, string>
     */
    private function form(array $override = []): array
    {
        return array_merge([
            'profile_name'        => 'Riley',
            'phone'               => '',
            'support_category_id' => (string) $this->categoryId(),
            'message'             => 'Something went wrong on my task list.',
        ], $override);
    }

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed(SupportCategorySeeder::class);
    }

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

    public function testCategoryDropdownLoadsApprovedTitlesFromDatabase(): void
    {
        $user = $this->createUser('support-flow-cats@example.com', 1);
        $page = $this->withSession($this->authSession($user))->get('support');
        $page->assertOK();
        $body = (string) $page->response()->getBody();

        $this->assertStringContainsString('id="support_category_id"', $body);

        $previous = 0;
        foreach (SupportService::APPROVED_TITLES as $title) {
            $page->assertSee($title);
            $pos = strpos($body, esc($title));
            $this->assertNotFalse($pos, $title . ' missing from dropdown');
            $this->assertGreaterThan($previous, $pos);
            $previous = $pos;
        }

        $this->assertSame(0, db_connect()->table('categories')->countAllResults());
    }

    public function testSavedNameEmailAndPhoneAreReadOnlyPrefill(): void
    {
        $user = $this->withProfile($this->createUser('locked@example.com', 1), [
            'profile_name' => 'Jordan Lee',
            'phone'        => '+1 202 555 0199',
        ]);
        $page = $this->withSession($this->authSession($user))->get('support');
        $page->assertOK();
        $page->assertSee('Jordan Lee');
        $page->assertSee('locked@example.com');
        $page->assertSee('+1 202 555 0199');
        $page->assertDontSee('name="email"');

        $body = (string) $page->response()->getBody();
        $this->assertMatchesRegularExpression('/id="support_profile_name"[^>]*\breadonly\b/s', $body);
        $this->assertDoesNotMatchRegularExpression('/id="support_profile_name"[^>]*\bname="profile_name"/s', $body);
        $this->assertMatchesRegularExpression('/id="support_email"[^>]*\breadonly\b/s', $body);
        $this->assertMatchesRegularExpression('/id="support_phone"[^>]*\breadonly\b/s', $body);
        $this->assertDoesNotMatchRegularExpression('/id="support_phone"[^>]*\bname="phone"/s', $body);
    }

    public function testMissingNameAndPhoneAreEditable(): void
    {
        $user = $this->createUser('editable@example.com', 1);
        $page = $this->withSession($this->authSession($user))->get('support');
        $page->assertOK();
        $body = (string) $page->response()->getBody();

        $this->assertMatchesRegularExpression('/id="support_profile_name"[^>]*\bname="profile_name"/s', $body);
        $this->assertDoesNotMatchRegularExpression('/id="support_profile_name"[^>]*\breadonly\b/s', $body);
        $this->assertMatchesRegularExpression('/id="support_phone"[^>]*\bname="phone"/s', $body);
        $this->assertDoesNotMatchRegularExpression('/id="support_phone"[^>]*\breadonly\b/s', $body);
        $this->assertMatchesRegularExpression('/id="support_email"[^>]*\breadonly\b/s', $body);
        $page->assertSee('editable@example.com');
        $page->assertDontSee('name="email"');
    }

    public function testValidationFailuresPreserveEditableValuesAndServerTruth(): void
    {
        $user = $this->withProfile($this->createUser('preserve@example.com', 1), [
            'profile_name' => 'True Name',
            'phone'        => '+1 555 0100',
        ]);
        $page = $this->withSession($this->authSession($user))->post('support', $this->form([
            'profile_name'        => 'Forged Name',
            'email'               => 'forged@example.com',
            'phone'               => '+44 0000',
            'support_category_id' => '',
            'message'             => 'Keep this text.',
        ]));
        $page->assertOK();
        $page->assertSee(SupportService::MSG_CATEGORY);
        $page->assertSee('True Name');
        $page->assertSee('preserve@example.com');
        $page->assertSee('+1 555 0100');
        $page->assertSee('Keep this text.');
        $page->assertDontSee('Forged Name');
        $page->assertDontSee('forged@example.com');
        $this->assertSame(0, db_connect()->table('support_requests')->countAllResults());
    }

    public function testMissingEditableNameAndInvalidPhoneAreRejected(): void
    {
        $user = $this->createUser('validate@example.com', 1);

        $missingName = $this->withSession($this->authSession($user))->post('support', $this->form([
            'profile_name' => '',
        ]));
        $missingName->assertSee(SupportService::MSG_NAME_REQUIRED);

        $whitespace = $this->withSession($this->authSession($user))->post('support', $this->form([
            'profile_name' => '    ',
        ]));
        $whitespace->assertSee(SupportService::MSG_NAME_REQUIRED);

        $badPhone = $this->withSession($this->authSession($user))->post('support', $this->form([
            'phone' => 'not-a-phone',
        ]));
        $badPhone->assertSee(ProfileService::MSG_PHONE_INVALID);
        $badPhone->assertSee('Riley');

        $blankMessage = $this->withSession($this->authSession($user))->post('support', $this->form([
            'message' => '',
        ]));
        $blankMessage->assertSee(SupportService::MSG_MESSAGE);

        $spaceMessage = $this->withSession($this->authSession($user))->post('support', $this->form([
            'message' => " \n ",
        ]));
        $spaceMessage->assertSee(SupportService::MSG_MESSAGE);

        $this->assertSame(0, db_connect()->table('support_requests')->countAllResults());
    }

    public function testValidSubmissionStoresSnapshotAndUsesPostRedirectGet(): void
    {
        $user = $this->withProfile($this->createUser('submit@example.com', 1), [
            'profile_name' => 'Sam Taylor',
            'phone'        => '+1 415 555 0133',
        ]);
        $categoryId = $this->categoryId('Task Management');

        $post = $this->withSession($this->authSession($user))->post('support', $this->form([
            'profile_name'        => 'Ignored Name',
            'email'               => 'attacker@example.com',
            'phone'               => '+44 20 7946 0958',
            'user_id'             => '99',
            'support_category_id' => (string) $categoryId,
            'message'             => 'Please look at Task Management.',
        ]));
        $post->assertRedirectTo('/support');
        $post->assertSessionHas('success', SupportService::MSG_SUCCESS);

        $this->seeInDatabase('support_requests', [
            'user_id'             => $user['id'],
            'profile_name'        => 'Sam Taylor',
            'email'               => 'submit@example.com',
            'phone'               => '+1 415 555 0133',
            'support_category_id' => $categoryId,
            'message'             => 'Please look at Task Management.',
            'status'              => 'open',
        ]);
        $this->assertSame(1, db_connect()->table('support_requests')->countAllResults());

        $refresh = $this->withSession($this->authSession($user))->get('support');
        $refresh->assertOK();
        $refreshBody = (string) $refresh->response()->getBody();
        $this->assertStringNotContainsString('Request #', $refreshBody);
        $this->assertStringNotContainsString('support_request_id', $refreshBody);
        $this->assertSame(1, db_connect()->table('support_requests')->countAllResults());
    }

    public function testSupportNameAndPhoneDoNotWriteBackToProfile(): void
    {
        $user = $this->createUser('no-writeback@example.com', 1);

        $this->withSession($this->authSession($user))->post('support', $this->form([
            'profile_name' => 'Only On Ticket',
            'phone'        => '+61 2 5550 1234',
            'message'      => 'I do not have a saved profile name.',
        ]))->assertRedirectTo('/support');

        $this->seeInDatabase('support_requests', [
            'user_id'      => $user['id'],
            'profile_name' => 'Only On Ticket',
            'email'        => 'no-writeback@example.com',
            'phone'        => '+61 2 5550 1234',
        ]);
        $saved = (new UserModel())->find($user['id']);
        $this->assertIsArray($saved);
        $this->assertTrue($saved['profile_name'] === null || $saved['profile_name'] === '');
        $this->assertTrue($saved['phone'] === null || $saved['phone'] === '');
    }

    public function testPostedUserIdCannotSubmitAsAnotherUser(): void
    {
        $alice = $this->withProfile($this->createUser('alice-support@example.com', 1), [
            'profile_name' => 'Alice',
        ]);
        $bob = $this->withProfile($this->createUser('bob-support@example.com', 1), [
            'profile_name' => 'Bob',
        ]);

        $this->withSession($this->authSession($alice))->post('support', $this->form([
            'user_id'      => (string) $bob['id'],
            'email'        => 'bob-support@example.com',
            'profile_name' => 'Not Bob',
            'message'      => 'Alice speaking.',
        ]))->assertRedirectTo('/support');

        $this->seeInDatabase('support_requests', [
            'user_id'      => $alice['id'],
            'profile_name' => 'Alice',
            'email'        => 'alice-support@example.com',
            'message'      => 'Alice speaking.',
        ]);
        $this->dontSeeInDatabase('support_requests', ['user_id' => $bob['id']]);
        $this->seeInDatabase('users', ['id' => $bob['id'], 'profile_name' => 'Bob']);
    }

    public function testXssPayloadsAreEscapedOnValidationRedisplay(): void
    {
        $user = $this->createUser('xss-support@example.com', 1);
        $page = $this->withSession($this->authSession($user))->post('support', $this->form([
            'profile_name' => '<script>alert(1)</script>',
            'message'      => '<img src=x onerror=alert(1)> Please help',
            'support_category_id' => '',
        ]));
        $page->assertOK();
        $body = (string) $page->response()->getBody();
        $this->assertStringNotContainsString('<script>alert(1)</script>', $body);
        $this->assertStringNotContainsString('<img src=x onerror=alert(1)>', $body);
        $this->assertStringContainsString('&lt;script&gt;alert(1)&lt;/script&gt;', $body);
        $this->assertStringContainsString('&lt;img src=x onerror=alert(1)&gt;', $body);
    }

    public function testMissingSupportCategoriesShowFriendlyState(): void
    {
        db_connect()->table('support_categories')->update(['is_active' => 0]);
        $user = $this->createUser('no-cats@example.com', 1);

        $page = $this->withSession($this->authSession($user))->get('support');
        $page->assertOK();
        $page->assertSee(SupportService::MSG_NO_CATEGORIES);
        $page->assertSee('disabled');

        $this->withSession($this->authSession($user))->post('support', [
            'profile_name'        => 'Casey',
            'support_category_id' => '1',
            'message'             => 'Still trying.',
        ])->assertSee(SupportService::MSG_NO_CATEGORIES);

        $this->assertSame(0, db_connect()->table('support_requests')->countAllResults());
    }

    public function testHamburgerContactSupportOpensSupportScreen(): void
    {
        $user = $this->createUser('menu-support@example.com', 1);
        $dash = $this->withSession($this->authSession($user))->get('dashboard');
        $dash->assertOK();
        $body = (string) $dash->response()->getBody();
        $this->assertSame(1, substr_count($body, 'Contact Support'));
        $this->assertStringContainsString('href="' . site_url('support') . '"', $body);

        $page = $this->withSession($this->authSession($user))->get('support');
        $page->assertOK();
        $page->assertSee('Contact Support');
        $page->assertSee('How can we help?');
        $page->assertSee('Submit Support Request');
    }

    public function testSettingsV1ContainsOnlyApprovedFields(): void
    {
        $user = $this->withProfile($this->createUser('settings-v1@example.com', 1), [
            'profile_name' => 'Pat Kim',
            'age_group'    => '25–34',
            'gender'       => 'Female',
            'occupation'   => 'Teacher',
            'phone'        => '+1 202 555 0111',
        ]);
        $page = $this->withSession($this->authSession($user))->get('settings');
        $page->assertOK();
        $page->assertSee('Profile Name');
        $page->assertSee('Age Group');
        $page->assertSee('Gender');
        $page->assertSee('Occupation');
        $page->assertSee('Phone');
        $page->assertSee('Email');
        $page->assertSee('settings-v1@example.com');
        $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');

        $config = new Tieasy();
        foreach ($config->ageGroups as $group) {
            $page->assertSee($group);
        }
        foreach ($config->genders as $gender) {
            $page->assertSee($gender);
        }

        $this->withSession($this->authSession($user))->post('settings/profile', [
            'profile_name' => 'Pat Updated',
            'age_group'    => '35–44',
            'gender'       => 'Prefer not to say',
            'occupation'   => 'Coach',
            'phone'        => '+1 202 555 0222',
            'email'        => 'attacker@example.com',
        ])->assertRedirectTo('/settings');

        $saved = (new UserModel())->find($user['id']);
        $this->assertIsArray($saved);
        $this->assertSame('Pat Updated', $saved['profile_name']);
        $this->assertSame('35–44', $saved['age_group']);
        $this->assertSame('Prefer not to say', $saved['gender']);
        $this->assertSame('Coach', $saved['occupation']);
        $this->assertSame('+1 202 555 0222', $saved['phone']);
        $this->assertSame('settings-v1@example.com', $saved['email']);
    }
}
