<?php

namespace Tests\Unit\Controllers;

use App\Auth\UseCases\AuthenticateUser\AuthenticateUser;
use App\Auth\UseCases\CreateSession\CreateSession;
use App\Auth\UseCases\Logout\Logout;
use App\Controllers\AuthController;
use App\Http\Middleware\AuthMiddleware;
use App\Shared\ValueObject\EmailAddress;
use App\User\CreateUserDto;
use DateTimeImmutable;
use DateTimeZone;
use Illuminate\Http\Request;
use Tests\Fakes\FakeClock;
use Tests\Fakes\FakeHasher;
use Tests\Fakes\FakeSessionRepository;
use Tests\Fakes\FakeTokenGenerator;
use Tests\Fakes\FakeUserRepository;
use Tests\TestCase;

class AuthControllerTest extends TestCase
{
    private AuthController $controller;

    private FakeUserRepository $userRepo;

    private FakeHasher $hasher;

    private FakeSessionRepository $sessionRepo;

    private FakeTokenGenerator $tokenGenerator;

    private FakeClock $clock;

    private DateTimeImmutable $now;

    protected function setUp(): void
    {
        $this->now = new DateTimeImmutable(
            '2026-04-29T12:00:00',
            new DateTimeZone('UTC')
        );
        $this->clock = new FakeClock($this->now);
        $this->tokenGenerator = new FakeTokenGenerator(['session-token-1']);
        $this->userRepo = new FakeUserRepository();
        $this->hasher = new FakeHasher();
        $this->sessionRepo = new FakeSessionRepository();
        $authenticateUser = new AuthenticateUser(
            $this->userRepo,
            $this->hasher,
        );
        $createSession = new CreateSession(
            $this->sessionRepo,
            $this->tokenGenerator,
            $this->clock,
        );
        $logout = new Logout($this->sessionRepo);
        $this->controller = new AuthController(
            $authenticateUser,
            $createSession,
            $logout,
        );
    }

    private function seedStartupUser(string $email, string $password): void
    {
        $user = $this->userRepo->create(
            new CreateUserDto(
                email: new EmailAddress($email),
                passwordHash: 'hashed-password',
            )
        );
    }

    public function test_login_returns_200_and_sets_cookie_on_success(): void
    {
        $email = 'user@example.com';
        $password = 'password';
        $this->seedStartupUser($email, $password);

        $request = new Request([
            'email' => $email,
            'password' => $password,
        ]);
        $response = $this->controller->login($request);

        $this->assertEquals(200, $response->getStatusCode());
        $body = json_decode($response->getContent(), true);
        $this->assertSame($email, $body['user']['email']);

        $cookies = $response->headers->getCookies();
        $this->assertCount(1, $cookies);
        $cookie = $cookies[0];
        $this->assertSame(
            AuthMiddleware::COOKIE_NAME,
            $cookie->getName()
        );
        $this->assertSame('session-token-1', $cookie->getValue());
        $this->assertTrue($cookie->isHttpOnly());
        $this->assertSame('lax', $cookie->getSameSite());
        $this->assertNotNull(
            $this->sessionRepo->findByToken('session-token-1')
        );
    }

    public function test_login_returns_400_when_email_missing(): void
    {
        $request = new Request(['password' => 'correctpassword']);
        $response = $this->controller->login($request);
        $this->assertEquals(400, $response->getStatusCode());
    }

    public function test_login_returns_400_when_password_missing(): void
    {
        $request = new Request(['email' => 'user@example.com']);
        $response = $this->controller->login($request);
        $this->assertEquals(400, $response->getStatusCode());
    }

    public function test_login_returns_401_when_credentials_invalid(): void
    {
        $this->seedStartupUser('user@example.com', 'correctpassword');

        $request = new Request([
            'email' => 'user@example.com',
            'password' => 'wrongpassword',
        ]);
        $response = $this->controller->login($request);
        $this->assertEquals(401, $response->getStatusCode());
    }

    public function test_logout_returns_204_and_clears_cookie(): void
    {
        $this->seedStartupUser('user@example.com', 'correctpassword');
        $loginRequest = new Request([
            'email' => 'user@example.com',
            'password' => 'correctpassword',
        ]);
        $this->controller->login($loginRequest);

        $logoutRequest = new Request;
        $logoutRequest->cookies->set(
            AuthMiddleware::COOKIE_NAME,
            'session-token-1'
        );
        $response = $this->controller->logout($logoutRequest);

        $this->assertEquals(204, $response->getStatusCode());
        $this->assertNull(
            $this->sessionRepo->findByToken('session-token-1')
        );

        $cookies = $response->headers->getCookies();
        $this->assertCount(1, $cookies);
        $this->assertSame(
            AuthMiddleware::COOKIE_NAME,
            $cookies[0]->getName()
        );
        $this->assertSame('', $cookies[0]->getValue());
    }

    public function test_me_returns_200_with_user_when_authenticated(): void
    {
        $email = 'me@example.com';
        $user = $this->userRepo->create(
            new CreateUserDto(
                email: new EmailAddress($email),
                passwordHash: 'password'
            )
        );

        $request = new Request;
        $request->attributes->set('user', $user);

        $response = $this->controller->me($request);

        $this->assertEquals(200, $response->getStatusCode());
        $body = json_decode($response->getContent(), true);
        $this->assertSame($user->getId(), $body['user']['id']);
        $this->assertSame($email, $body['user']['email']);
    }
}
