<?php

declare(strict_types=1);

namespace ApiModel\Tests\Integration\Http;

use ApiModel\Action\AuthAction;
use ApiModel\Action\HealthAction;
use ApiModel\Action\MetricsAction;
use ApiModel\Action\UserAction;
use ApiModel\Route\AuthRoutes;
use ApiModel\Route\CoreRoutes;
use ApiModel\Route\DocsRoutes;
use ApiModel\Route\MetricsRoutes;
use ApiModel\Route\UserRoutes;
use ApiModel\Service\AuthService;
use ApiModel\Service\MenuService;
use ApiModel\Service\UserService;
use ApiModel\Infrastructure\Persistence\Repositories\PdoModuleRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoPageRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoProfileRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoPasswordResetRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoPlatformAuditRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoRefreshTokenRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoUserRepository;
use Firebase\JWT\JWT;
use PDO;
use PHPUnit\Framework\TestCase;
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Psr7\Factory\ServerRequestFactory;
use Slim\Psr7\Factory\StreamFactory;

final class HttpRoutesTest extends TestCase
{
    private PDO $pdo;
    private AuthService $authService;
    private App $app;

    protected function setUp(): void
    {
        parent::setUp();

        $this->pdo = new PDO('sqlite::memory:');
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

        $this->createSchema();
        $this->seedData();
        $this->configureEnv();

        $this->authService = new AuthService(
            new PdoUserRepository($this->pdo),
            new PdoRefreshTokenRepository($this->pdo),
            new PdoPasswordResetRepository($this->pdo),
            new PdoPlatformAuditRepository($this->pdo),
            null,
        );
        $this->app = $this->buildApp();
    }

    public function testPostAuthLoginReturns200WithJwt(): void
    {
        $response = $this->jsonRequest('POST', '/auth/login', [
            'email' => 'admin@example.com',
            'password' => 'Secret123!',
        ]);

        $this->assertSame(200, $response->getStatusCode());
        $body = $this->decodeBody($response);
        $this->assertArrayHasKey('access_token', $body);
        $this->assertArrayHasKey('refresh_token', $body);
        $this->assertArrayHasKey('menu', $body);
        $this->assertIsArray($body['menu']);
    }

    public function testPostAuthLoginWrongPasswordReturns401WithUnauthorizedCode(): void
    {
        $response = $this->jsonRequest('POST', '/auth/login', [
            'email' => 'manager@example.com',
            'password' => 'incorrect',
        ]);

        $this->assertSame(401, $response->getStatusCode());
        $body = $this->decodeBody($response);
        $this->assertFalse($body['success'] ?? true);
        $this->assertSame('UNAUTHORIZED', $body['error']['code'] ?? '');
    }

    public function testPostAuthLoginWithoutBodyReturns422(): void
    {
        $response = $this->jsonRequest('POST', '/auth/login', []);
        $this->assertSame(422, $response->getStatusCode());
    }

    public function testGetMetricsWithoutSecretReturns401(): void
    {
        $response = $this->request('GET', '/metrics');
        $this->assertSame(401, $response->getStatusCode());
    }

    public function testGetMetricsWithSecretReturns200(): void
    {
        $response = $this->request('GET', '/metrics', [
            'X-Metrics-Key' => 'test-metrics-secret',
        ]);
        $this->assertSame(200, $response->getStatusCode());
        $body = $this->decodeBody($response);
        $this->assertArrayHasKey('enabled', $body);
    }

    public function testUnknownRouteReturns404(): void
    {
        $response = $this->request('GET', '/ruta-no-existe');
        $this->assertSame(404, $response->getStatusCode());
    }

    public function testGetUsersMeWithJwtReturns200(): void
    {
        $response = $this->request('GET', '/users/me', [
            'Authorization' => 'Bearer ' . $this->issueToken('u-manager-1', 'MANAGER'),
        ]);
        $this->assertSame(200, $response->getStatusCode());
        $body = $this->decodeBody($response);
        $this->assertSame('manager@example.com', $body['email'] ?? null);
    }

    public function testPatchUsersMeLanguageInvalidReturns400WithBadRequestCode(): void
    {
        $response = $this->jsonRequest(
            'PATCH',
            '/users/me/language',
            ['language' => 'fr'],
            ['Authorization' => 'Bearer ' . $this->issueToken('u-manager-1', 'MANAGER')],
        );
        $this->assertSame(400, $response->getStatusCode());
        $body = $this->decodeBody($response);
        $this->assertFalse($body['success'] ?? true);
        $this->assertSame('BAD_REQUEST', $body['error']['code'] ?? '');
    }

    public function testPatchUsersMePhotoStoresDataUrl(): void
    {
        $photo = 'data:image/png;base64,' . base64_encode('fake-image-bytes');
        $response = $this->jsonRequest(
            'PATCH',
            '/users/me/photo',
            ['photo' => $photo],
            ['Authorization' => 'Bearer ' . $this->issueToken('u-manager-1', 'MANAGER')],
        );
        $this->assertSame(200, $response->getStatusCode());
        $body = $this->decodeBody($response);
        $this->assertSame($photo, $body['photo'] ?? null);

        $me = $this->request('GET', '/users/me', [
            'Authorization' => 'Bearer ' . $this->issueToken('u-manager-1', 'MANAGER'),
        ]);
        $profile = $this->decodeBody($me);
        $this->assertSame($photo, $profile['photo'] ?? null);
    }

    public function testGetOpenApiJsonIsGeneratedFromRoutes(): void
    {
        $response = $this->request('GET', '/docs/openapi.json');
        $this->assertSame(200, $response->getStatusCode());
        $body = $this->decodeBody($response);
        $this->assertSame('3.0.3', $body['openapi'] ?? null);
        $this->assertIsArray($body['paths'] ?? null);
        $this->assertArrayHasKey('/auth/login', $body['paths']);
        $this->assertArrayHasKey('post', $body['paths']['/auth/login']);
    }

    private function buildApp(): App
    {
        $app = AppFactory::create();

        $healthAction = new HealthAction();
        $menuService = new MenuService(
            new PdoModuleRepository($this->pdo),
            new PdoPageRepository($this->pdo),
            new PdoProfileRepository($this->pdo),
        );
        $authAction = new AuthAction($this->authService, $menuService);
        $userAction = new UserAction(new UserService(new PdoUserRepository($this->pdo)));
        $metricsAction = new MetricsAction();

        $app->group('', function ($g) use (
            $app,
            $healthAction,
            $authAction,
            $userAction,
            $metricsAction,
        ): void {
            DocsRoutes::register($app, $g);
            CoreRoutes::register($g, $healthAction);
            AuthRoutes::register($g, $authAction);
            UserRoutes::register($g, $userAction);
            MetricsRoutes::register($g, $metricsAction);
        });

        $app->addRoutingMiddleware();
        $app->addErrorMiddleware(false, true, true);

        return $app;
    }

    private function request(string $method, string $path, array $headers = []): \Psr\Http\Message\ResponseInterface
    {
        $requestFactory = new ServerRequestFactory();
        $request = $requestFactory->createServerRequest($method, $path);
        foreach ($headers as $name => $value) {
            $request = $request->withHeader($name, $value);
        }

        return $this->app->handle($request);
    }

    private function jsonRequest(string $method, string $path, array $payload, array $headers = []): \Psr\Http\Message\ResponseInterface
    {
        $streamFactory = new StreamFactory();
        $requestFactory = new ServerRequestFactory();
        $body = $streamFactory->createStream((string) json_encode($payload, JSON_THROW_ON_ERROR));

        $request = $requestFactory
            ->createServerRequest($method, $path)
            ->withHeader('Content-Type', 'application/json')
            ->withBody($body);

        foreach ($headers as $name => $value) {
            $request = $request->withHeader($name, $value);
        }

        return $this->app->handle($request);
    }

    /** @return array<string,mixed> */
    private function decodeBody(\Psr\Http\Message\ResponseInterface $response): array
    {
        $raw = (string) $response->getBody();
        $data = json_decode($raw, true);
        return is_array($data) ? $data : [];
    }

    private function issueToken(string $userId, string $role): string
    {
        return JWT::encode([
            'sub' => $userId,
            'role' => $role,
            'lang' => 'es',
            'iat' => time(),
            'exp' => time() + 3600,
        ], (string) $_ENV['JWT_SECRET'], 'HS256');
    }

    private function createSchema(): void
    {
        $this->pdo->exec("ATTACH DATABASE ':memory:' AS auth");

        $this->pdo->exec(
            'CREATE TABLE auth.roles (
                id TEXT PRIMARY KEY,
                name_es TEXT NOT NULL UNIQUE,
                name_en TEXT NOT NULL UNIQUE,
                description_es TEXT,
                description_en TEXT,
                is_active INTEGER NOT NULL DEFAULT 1,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                updated_at TEXT DEFAULT CURRENT_TIMESTAMP
            )',
        );

        $this->pdo->exec(
            'CREATE TABLE auth.users (
                id TEXT PRIMARY KEY,
                name TEXT NOT NULL,
                email TEXT NOT NULL UNIQUE,
                password_hash TEXT NULL,
                external_id TEXT NULL,
                language TEXT NOT NULL DEFAULT "es",
                photo TEXT NULL,
                is_active INTEGER NOT NULL DEFAULT 1,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                updated_at TEXT NULL
            )',
        );

        $this->pdo->exec(
            'CREATE TABLE auth.role_user (
                role_id TEXT NOT NULL,
                user_id TEXT NOT NULL,
                PRIMARY KEY (role_id, user_id)
            )',
        );

        $this->pdo->exec(
            'CREATE TABLE auth.refresh_tokens (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id TEXT NOT NULL,
                token_hash TEXT NOT NULL,
                expires_at TEXT NOT NULL,
                revoked INTEGER NOT NULL DEFAULT 0
            )',
        );

        $this->pdo->exec(
            'CREATE TABLE auth.password_reset_codes (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                email TEXT NOT NULL,
                code_hash TEXT NOT NULL,
                expires_at TEXT NOT NULL,
                consumed_at TEXT NULL
            )',
        );

        $this->pdo->exec(
            'CREATE TABLE notifications (
                id TEXT PRIMARY KEY,
                user_id TEXT NOT NULL,
                type TEXT NOT NULL,
                title TEXT NOT NULL,
                body TEXT NOT NULL,
                claim_id TEXT NULL,
                is_read INTEGER NOT NULL DEFAULT 0,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP
            )',
        );

        $this->pdo->exec(
            'CREATE TABLE auth.logs (
                id TEXT PRIMARY KEY,
                action TEXT NOT NULL,
                actor_user_id TEXT NULL,
                target_id TEXT NULL,
                metadata TEXT NULL,
                ip_address TEXT NULL,
                user_agent TEXT NULL,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP
            )',
        );

        $this->pdo->exec(
            'CREATE TABLE auth.module (
                id TEXT PRIMARY KEY,
                name_es TEXT NOT NULL UNIQUE,
                name_en TEXT NOT NULL UNIQUE,
                icon TEXT,
                order_index INTEGER DEFAULT 0,
                is_active INTEGER NOT NULL DEFAULT 1,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                updated_at TEXT DEFAULT CURRENT_TIMESTAMP
            )',
        );

        $this->pdo->exec(
            'CREATE TABLE auth.page (
                id TEXT PRIMARY KEY,
                module_id TEXT NOT NULL,
                name_es TEXT NOT NULL,
                name_en TEXT NOT NULL,
                path TEXT NOT NULL UNIQUE,
                icon TEXT,
                order_index INTEGER DEFAULT 0,
                is_active INTEGER NOT NULL DEFAULT 1,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                updated_at TEXT DEFAULT CURRENT_TIMESTAMP
            )',
        );

        $this->pdo->exec(
            'CREATE TABLE auth.profile (
                id TEXT PRIMARY KEY,
                role_id TEXT NOT NULL,
                page_id TEXT NOT NULL,
                can_view INTEGER NOT NULL DEFAULT 0,
                can_create INTEGER NOT NULL DEFAULT 0,
                can_edit INTEGER NOT NULL DEFAULT 0,
                can_delete INTEGER NOT NULL DEFAULT 0,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
                UNIQUE (role_id, page_id)
            )',
        );
    }

    private function seedData(): void
    {
        $hash = password_hash('Secret123!', PASSWORD_BCRYPT, ['cost' => 10]);

        $this->pdo->prepare(
            'INSERT INTO auth.users (id, name, email, password_hash, external_id, is_active)
             VALUES (?,?,?,?,?,1)',
        )->execute(['u-manager-1', 'Manager Uno', 'manager@example.com', $hash, null]);

        $this->pdo->prepare(
            'INSERT INTO auth.users (id, name, email, password_hash, external_id, is_active)
             VALUES (?,?,?,?,?,1)',
        )->execute(['u-admin-1', 'Admin Uno', 'admin@example.com', $hash, null]);

        $this->pdo->prepare(
            'INSERT INTO auth.users (id, name, email, password_hash, external_id, is_active)
             VALUES (?,?,?,?,?,1)',
        )->execute(['u-client-1', 'Cliente Uno', 'client@example.com', $hash, 'C-001']);

        // Insert roles
        $this->pdo->prepare(
            'INSERT INTO auth.roles (id, name_es, name_en, description_es, description_en)
             VALUES (?,?,?,?,?)'
        )->execute(['r-manager-1', 'MANAGER', 'MANAGER', 'Manager', 'Manager']);

        $this->pdo->prepare(
            'INSERT INTO auth.roles (id, name_es, name_en, description_es, description_en)
             VALUES (?,?,?,?,?)'
        )->execute(['r-admin-1', 'ADMINISTRADOR', 'ADMINISTRADOR', 'Admin', 'Admin']);

        $this->pdo->prepare(
            'INSERT INTO auth.roles (id, name_es, name_en, description_es, description_en)
             VALUES (?,?,?,?,?)'
        )->execute(['r-client-1', 'CLIENTE', 'CLIENTE', 'Cliente', 'Client']);

        // Link roles
        $this->pdo->prepare('INSERT INTO auth.role_user (role_id, user_id) VALUES (?,?)')
            ->execute(['r-manager-1', 'u-manager-1']);

        $this->pdo->prepare('INSERT INTO auth.role_user (role_id, user_id) VALUES (?,?)')
            ->execute(['r-admin-1', 'u-admin-1']);

        $this->pdo->prepare('INSERT INTO auth.role_user (role_id, user_id) VALUES (?,?)')
            ->execute(['r-client-1', 'u-client-1']);
    }

    private function configureEnv(): void
    {
        $_ENV['JWT_SECRET'] = 'test-jwt-secret';
        $_ENV['JWT_EXPIRES_IN'] = '15m';
        $_ENV['JWT_REFRESH_EXPIRES_DAYS'] = '7';
        $_ENV['JWT_KID'] = '';
        $_ENV['JWT_SECRET_PREVIOUS'] = '';
        $_ENV['PASSWORD_RESET_CODE_SECRET'] = 'test-reset-secret';
        $_ENV['PASSWORD_RESET_CODE_TTL_MIN'] = '15';
        $_ENV['EXTERNAL_APP_MOCK'] = 'false';
        $_ENV['EXTERNAL_APP_API_URL'] = '';
        $_ENV['METRICS_ENABLED'] = 'true';
        $_ENV['METRICS_SECRET'] = 'test-metrics-secret';
    }
}

