<?php

declare(strict_types=1);

namespace Tests\Feature;

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

/**
 * POST /api/auth/register — Registration Test Suite
 *
 * Covers:
 *  ✓ Successful registration with all valid fields
 *  ✓ Response structure validation (201 + user object)
 *  ✓ Password hashing — plaintext MUST NOT appear in any response
 *  ✓ Minimum password length boundary (8 chars)
 *  ✓ Duplicate email constraint (409 Conflict)
 *  ✓ Duplicate username constraint (409 Conflict)
 *  ✓ Missing required fields (400 Bad Request)
 *  ✓ Invalid email format rejection (400)
 *  ✓ Role assignment in response
 *
 * @group registration
 */
#[Group('registration')]
class RegisterTest extends AuthTestCase
{
    // ─── Happy Path ───────────────────────────────────────────────────────────

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

        $this->assertTrue(
            $response->isCreated(),
            "Expected HTTP 201, got {$response->status}. Body: " . $response->dump()
        );

        $this->assertArrayHasKey(
            'user',
            $response->data,
            "Response must contain a 'user' key."
        );

        $user = $response->get('user');
        $this->assertIsArray($user);
    }

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

        $this->assertTrue($response->isCreated(), $response->dump());

        $user = $response->get('user');
        $this->assertHasKeys(['id', 'name', 'email'], $user);
        $this->assertSame($payload['email'], $user['email']);
        $this->assertSame($payload['name'],  $user['name']);
    }

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

        $this->assertStringNotContainsString(
            $payload['password'],
            $response->rawBody,
            "Plaintext password MUST NOT appear anywhere in the HTTP response body."
        );
    }

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

        $this->assertTrue($response->isCreated(), $response->dump());
        $this->assertNotEmpty($response->get('message'));
    }

    #[Test]
    public function registration_assigns_default_role_when_not_specified(): void
    {
        $payload = TestDataFactory::makeUser();
        unset($payload['role_id']); // Let the service apply default

        $response = $this->registerUser($payload);
        $this->assertTrue($response->isCreated(), $response->dump());

        $user = $response->get('user');
        $this->assertArrayHasKey('role_id', $user, "role_id must be present in user response.");
        $this->assertNotEmpty($user['role_id']);
    }

    // ─── Password Boundary Tests ──────────────────────────────────────────────

    #[Test]
    public function password_shorter_than_8_chars_is_rejected_with_400(): void
    {
        $payload  = TestDataFactory::makeWeakPasswordUser(); // password = 'weak'
        $response = $this->registerUser($payload);

        $this->assertTrue(
            $response->isBadRequest(),
            "Expected 400 for short password, got {$response->status}. Body: " . $response->dump()
        );
        $this->assertStringContainsStringIgnoringCase(
            'password',
            $response->rawBody
        );
    }

    #[Test]
    public function password_of_exactly_8_chars_is_accepted(): void
    {
        $payload             = TestDataFactory::makeUser();
        $payload['password'] = 'Aa1!abcd'; // exactly 8 characters

        $response = $this->registerUser($payload);
        $this->assertTrue($response->isCreated(), $response->dump());
    }

    #[Test]
    public function password_of_7_chars_is_rejected(): void
    {
        $payload             = TestDataFactory::makeUser();
        $payload['password'] = 'Aa1!abc'; // 7 characters

        $response = $this->registerUser($payload);
        $this->assertTrue($response->isBadRequest(), $response->dump());
    }

    // ─── Duplicate Constraint Tests ───────────────────────────────────────────

    #[Test]
    public function duplicate_email_returns_409_conflict(): void
    {
        $payload = TestDataFactory::makeUser();

        // First registration — must succeed
        $first = $this->registerUser($payload);
        $this->assertTrue($first->isCreated(), "First registration failed: " . $first->dump());

        // Second registration with same email — must conflict
        $duplicate             = TestDataFactory::makeUser(); // fresh name/phone/etc.
        $duplicate['email']    = $payload['email'];           // same email

        $second = $this->registerUser($duplicate);
        $this->assertTrue(
            $second->isConflict(),
            "Expected 409 Conflict for duplicate email, got {$second->status}."
        );
    }

    // ─── Validation Error Tests ───────────────────────────────────────────────

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

        $this->assertTrue(
            $response->isBadRequest(),
            "Expected 400 for invalid email, got {$response->status}."
        );
        $this->assertStringContainsStringIgnoringCase('email', $response->rawBody);
    }

    #[Test]
    #[DataProvider('missingFieldProvider')]
    public function missing_required_field_returns_400(string $missingField): void
    {
        $payload = TestDataFactory::makeMissingFieldUser($missingField);
        $response = $this->registerUser($payload);

        $this->assertTrue(
            $response->isBadRequest(),
            "Expected 400 when '{$missingField}' is missing, got {$response->status}."
        );
    }

    public static function missingFieldProvider(): array
    {
        return [
            'missing name'     => ['name'],
            'missing email'    => ['email'],
            'missing password' => ['password'],
            'missing phone'    => ['phone'],
            'missing address'  => ['address'],
        ];
    }

    #[Test]
    public function empty_request_body_returns_400(): void
    {
        $response = $this->registerUser([]);
        $this->assertTrue($response->isBadRequest(), $response->dump());
    }
}
