<?php

declare(strict_types=1);

namespace Tests\Feature;

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\Group;
use Tests\AuthTestCase;
use Tests\Support\TestDataFactory;

/**
 * POST /api/auth/login — Login & Token Structure Test Suite
 *
 * Covers:
 *  ✓ Successful login returns 200 + token
 *  ✓ Token present and non-empty in response
 *  ✓ Wrong password returns 401
 *  ✓ Non-existent email returns 401
 *  ✓ Missing email/password fields return 400
 *  ✓ Response user object matches registered credentials
 *  ✓ Password never exposed in login response
 *  ⚠ Suspended account blocked (requires status check in controller — current gap)
 *  ⚠ Banned account blocked (requires status check in controller — current gap)
 *
 * @group login
 */
#[Group('login')]
class LoginTest extends AuthTestCase
{
    // ─── Happy Path ───────────────────────────────────────────────────────────

    #[Test]
    public function valid_credentials_return_200_with_token(): void
    {
        $payload  = TestDataFactory::makeUser();
        $register = $this->registerUser($payload);
        $this->assertTrue($register->isCreated(), "Setup registration failed: " . $register->dump());

        $login = $this->loginAs($payload['email'], $payload['password']);

        $this->assertTrue(
            $login->isOk(),
            "Expected HTTP 200, got {$login->status}. Body: " . $login->dump()
        );

        $token = $login->get('token') ?? $login->get('access_token');
        $this->assertNotEmpty($token, "Login response must include a non-empty token.");
    }

    #[Test]
    public function login_response_contains_user_object_with_correct_email(): void
    {
        $payload  = TestDataFactory::makeUser();
        $this->registerUser($payload);

        $login = $this->loginAs($payload['email'], $payload['password']);
        $this->assertTrue($login->isOk(), $login->dump());

        $user = $login->get('user');
        $this->assertIsArray($user, "Login response must contain a 'user' object.");
        $this->assertSame($payload['email'], $user['email']);
    }

    #[Test]
    public function login_response_never_exposes_hashed_password(): void
    {
        $payload  = TestDataFactory::makeUser();
        $this->registerUser($payload);

        $login = $this->loginAs($payload['email'], $payload['password']);

        $this->assertStringNotContainsString(
            $payload['password'],
            $login->rawBody,
            "Plaintext password must never appear in any HTTP response."
        );

        // Hashed passwords start with $2y$ — verify no bcrypt hash leaks either
        $this->assertStringNotContainsString(
            '$2y$',
            $login->rawBody,
            "Bcrypt hash must not be exposed in login response."
        );
    }

    #[Test]
    public function successful_login_returns_a_success_message(): void
    {
        $payload = TestDataFactory::makeUser();
        $this->registerUser($payload);

        $login = $this->loginAs($payload['email'], $payload['password']);
        $this->assertTrue($login->isOk(), $login->dump());
        $this->assertNotEmpty($login->get('message'));
    }

    // ─── Negative / Rejection Tests ───────────────────────────────────────────

    #[Test]
    public function wrong_password_returns_401(): void
    {
        $payload  = TestDataFactory::makeUser();
        $this->registerUser($payload);

        $login = $this->loginAs($payload['email'], 'WrongPass@999!');

        $this->assertTrue(
            $login->isUnauth(),
            "Expected 401 for wrong password, got {$login->status}."
        );
    }

    #[Test]
    public function non_existent_email_returns_401(): void
    {
        $login = $this->loginAs(
            'ghost_' . TestDataFactory::uid() . '@sms.testdomain.local',
            'SomePassword@1!'
        );

        $this->assertTrue(
            $login->isUnauth(),
            "Expected 401 for unknown email, got {$login->status}."
        );
    }

    #[Test]
    public function missing_email_field_returns_400(): void
    {
        $response = static::$http->post('/api/auth/login', ['password' => 'SomePass@1!']);
        $this->assertTrue($response->isBadRequest(), $response->dump());
    }

    #[Test]
    public function missing_password_field_returns_400(): void
    {
        $response = static::$http->post('/api/auth/login', [
            'email' => 'someone@sms.testdomain.local',
        ]);
        $this->assertTrue($response->isBadRequest(), $response->dump());
    }

    #[Test]
    public function empty_body_returns_400(): void
    {
        $response = static::$http->post('/api/auth/login', []);
        $this->assertTrue($response->isBadRequest(), $response->dump());
    }

    // ─── Account State / Status Gating (Implementation Gap Tests) ─────────────
    // These tests document REQUIRED security behaviour.
    // They will FAIL until the controller's login() method adds a status_id check.
    // Reference: auth-sql.em STATUS_SUSPENDED = 142345678901234593

    #[Test]
    public function suspended_account_is_blocked_from_login(): void
    {
        $this->markTestSkipped(
            'IMPLEMENTATION GAP: Login controller does not yet query status_id. ' .
            'Add: SELECT ... JOIN statuses WHERE slug NOT IN (\'suspended\',\'banned\') to fix.'
        );

        // ── When implemented, this is the test body: ──
        $payload = TestDataFactory::makeUser();
        $this->registerUser($payload);

        // Directly update the user's status to 'suspended' via DB
        static::$db->prepare(
            "UPDATE auth_users SET status_id = :sid WHERE email = :email"
        )->execute([
            'sid'   => TestDataFactory::STATUS_SUSPENDED,
            'email' => $payload['email'],
        ]);

        $login = $this->loginAs($payload['email'], $payload['password']);
        $this->assertTrue($login->isUnauth(), "Suspended account must return 401.");
        $this->assertStringContainsStringIgnoringCase('suspend', $login->rawBody);
    }

    #[Test]
    public function banned_account_is_blocked_from_login(): void
    {
        $this->markTestSkipped(
            'IMPLEMENTATION GAP: Login controller does not yet query status_id. ' .
            'Add status check to login() to unblock this test.'
        );

        $payload = TestDataFactory::makeUser();
        $this->registerUser($payload);

        static::$db->prepare(
            "UPDATE auth_users SET status_id = :sid WHERE email = :email"
        )->execute([
            'sid'   => TestDataFactory::STATUS_BANNED,
            'email' => $payload['email'],
        ]);

        $login = $this->loginAs($payload['email'], $payload['password']);
        $this->assertTrue($login->isUnauth(), "Banned account must return 401.");
    }
}
