<?php

declare(strict_types=1);

namespace ApiModel\Tests\Integration\Http;

use ApiModel\Action\SecurityAction;
use ApiModel\Route\SecurityRoutes;
use ApiModel\Service\RoleService;
use ApiModel\Service\MenuService;
use ApiModel\Service\MessageService;
use ApiModel\Infrastructure\Persistence\Repositories\PdoRoleRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoModuleRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoPageRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoProfileRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoMessageRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoUserRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoPlatformAuditRepository;
use ApiModel\Service\UserService;
use ApiModel\Service\AuditLogService;
use ApiModel\Service\AuthService;
use ApiModel\Infrastructure\Persistence\Repositories\PdoPasswordResetRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoRefreshTokenRepository;
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 SecurityRoutesTest extends TestCase
{
    private PDO $pdo;
    private App $app;
    private string $adminToken;
    private string $managerToken;

    private const ADMIN_USER_ID = '11111111-1111-1111-1111-111111111111';
    private const MANAGER_USER_ID = '22222222-2222-2222-2222-222222222222';
    private const ADMIN_ROLE_ID = '33333333-3333-3333-3333-333333333333';
    private const MANAGER_ROLE_ID = '44444444-4444-4444-4444-444444444444';
    private const CLIENT_ROLE_ID = '55555555-5555-5555-5555-555555555555';
    private const CONFIG_MODULE_ID = '66666666-6666-6666-6666-666666666666';
    private const USERS_PAGE_ID = '77777777-7777-7777-7777-777777777777';
    private const PROFILE_ID = '88888888-8888-8888-8888-888888888888';

    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->app = $this->buildApp();

        $this->adminToken = $this->issueToken(self::ADMIN_USER_ID, 'ADMINISTRADOR');
        $this->managerToken = $this->issueToken(self::MANAGER_USER_ID, 'MANAGER');
    }

    public function testGetMenuReturnsMenuForAuthenticatedUser(): void
    {
        $response = $this->request('GET', '/auth/menu', [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);

        $this->assertSame(200, $response->getStatusCode());
        $body = $this->decodeBody($response);

        // Debería tener el módulo de configuración y la página de usuarios
        $this->assertIsArray($body);
        $this->assertCount(1, $body);
        $this->assertSame('Configuración', $body[0]['name']);
        $this->assertCount(1, $body[0]['pages']);
        $this->assertSame('Usuarios', $body[0]['pages'][0]['name']);
        $this->assertTrue($body[0]['pages'][0]['permissions']['can_view']);
    }

    public function testGetMenuReturnsEmptyMenuForUserWithoutPermissions(): void
    {
        $response = $this->request('GET', '/auth/menu', [
            'Authorization' => 'Bearer ' . $this->managerToken,
        ]);

        $this->assertSame(200, $response->getStatusCode());
        $body = $this->decodeBody($response);

        $this->assertIsArray($body);
        $this->assertCount(0, $body); // Manager no tiene permisos asignados en profile
    }

    public function testListRolesAsAdminReturnsRoles(): void
    {
        $response = $this->request('GET', '/security/roles', [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);

        $this->assertSame(200, $response->getStatusCode());
        $body = $this->decodeBody($response);

        $this->assertIsArray($body);
        $this->assertCount(3, $body);
    }

    public function testListRolesAsManagerReturns403Forbidden(): void
    {
        $response = $this->request('GET', '/security/roles', [
            'Authorization' => 'Bearer ' . $this->managerToken,
        ]);

        $this->assertSame(403, $response->getStatusCode());
    }

    public function testCreateRoleAsAdmin(): void
    {
        $response = $this->jsonRequest('POST', '/security/roles', [
            'name_es' => 'Contador',
            'name_en' => 'Accountant',
            'description_es' => 'Acceso contable',
            'description_en' => 'Accounting access',
        ], [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);

        $this->assertSame(200, $response->getStatusCode());
        $body = $this->decodeBody($response);

        $this->assertSame('Contador', $body['name_es']);
        $this->assertSame('Accountant', $body['name_en']);
    }

    public function testSaveRolePermissions(): void
    {
        // Asignar permiso a MANAGER para ver la página de usuarios
        $response = $this->jsonRequest('POST', '/security/roles/' . self::MANAGER_ROLE_ID . '/permissions', [
            'page_id' => self::USERS_PAGE_ID,
            'can_view' => true,
            'can_create' => false,
            'can_edit' => false,
            'can_delete' => false,
        ], [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);

        $this->assertSame(200, $response->getStatusCode());
        $body = $this->decodeBody($response);
        $this->assertTrue($body['ok'] ?? false);

        // Ahora el manager debería poder ver el menú
        $menuResponse = $this->request('GET', '/auth/menu', [
            'Authorization' => 'Bearer ' . $this->managerToken,
        ]);
        $menuBody = $this->decodeBody($menuResponse);
        $this->assertCount(1, $menuBody);
        $this->assertSame('Configuración', $menuBody[0]['name']);
    }

    public function testManageMessages(): void
    {
        // Crear plantilla
        $createRes = $this->jsonRequest('POST', '/security/messages', [
            'code' => 'PASSWORD_RESET',
            'subject_es' => 'Código de recuperación',
            'subject_en' => 'Recovery Code',
            'body_html_es' => '<p>Tu código es {{code}}</p>',
            'body_html_en' => '<p>Your code is {{code}}</p>',
        ], [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);

        $this->assertSame(200, $createRes->getStatusCode());

        // Obtener plantilla
        $getRes = $this->request('GET', '/security/messages/PASSWORD_RESET', [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);
        $this->assertSame(200, $getRes->getStatusCode());
        $getBody = $this->decodeBody($getRes);
        $this->assertSame('Código de recuperación', $getBody['subject_es']);
    }

    public function testManageUsers(): void
    {
        // 1. Listar usuarios (inicialmente 2)
        $listRes = $this->request('GET', '/security/users', [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);
        $this->assertSame(200, $listRes->getStatusCode());
        $listBody = $this->decodeBody($listRes);
        $this->assertCount(2, $listBody);

        // 2. Crear usuario nuevo
        $createRes = $this->jsonRequest('POST', '/security/users', [
            'name' => 'New User',
            'email' => 'newuser@example.com',
            'roles' => [
                ['id' => self::MANAGER_ROLE_ID, 'activo' => true]
            ]
        ], [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);
        $this->assertSame(200, $createRes->getStatusCode());
        $createBody = $this->decodeBody($createRes);
        $newUserId = $createBody['id'];

        $st = $this->pdo->prepare('SELECT password_hash FROM auth.users WHERE id = ?');
        $st->execute([$newUserId]);
        $this->assertNull($st->fetchColumn());

        $rst = $this->pdo->prepare('SELECT COUNT(*) FROM auth.password_reset_codes WHERE email = ?');
        $rst->execute(['newuser@example.com']);
        $this->assertSame(1, (int) $rst->fetchColumn());

        // 3. Modificar usuario
        $updateRes = $this->jsonRequest('PATCH', '/security/users/' . $newUserId, [
            'name' => 'Updated User',
            'email' => 'updated@example.com',
            'activo' => false,
            'roles' => [
                ['id' => self::CLIENT_ROLE_ID, 'activo' => true]
            ]
        ], [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);
        $this->assertSame(200, $updateRes->getStatusCode());
        $updateBody = $this->decodeBody($updateRes);
        $this->assertSame('Updated User', $updateBody['name']);
        $this->assertSame('updated@example.com', $updateBody['email']);

        // 4. Eliminar usuario
        $deleteRes = $this->request('DELETE', '/security/users/' . $newUserId, [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);
        $this->assertSame(200, $deleteRes->getStatusCode());
    }

    public function testManageLogs(): void
    {
        // Insertar log de prueba
        $this->pdo->prepare(
            'INSERT INTO auth.logs (id, action, actor_user_id, metadata, ip_address, user_agent) VALUES (?,?,?,?,?,?)'
        )->execute([
            'log-1',
            'USER_LOGIN',
            self::ADMIN_USER_ID,
            json_encode(['method' => 'POST', 'message' => 'Logged in']),
            '127.0.0.1',
            'Mozilla/5.0'
        ]);

        // 1. Listar resumen de logs
        $res = $this->request('GET', '/security/logs', [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);
        $this->assertSame(200, $res->getStatusCode());
        $body = $this->decodeBody($res);
        $this->assertNotEmpty($body);

        // 2. Listar detalle de logs
        $detailRes = $this->jsonRequest('POST', '/security/logs/detail', [
            'id' => self::ADMIN_USER_ID
        ], [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);
        $this->assertSame(200, $detailRes->getStatusCode());
        $detailBody = $this->decodeBody($detailRes);
        $this->assertCount(1, $detailBody);
        $this->assertSame('POST', $detailBody[0]['method']);
    }

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

        $roleService = new RoleService(
            new PdoRoleRepository($this->pdo),
            new PdoProfileRepository($this->pdo)
        );
        $menuService = new MenuService(
            new PdoModuleRepository($this->pdo),
            new PdoPageRepository($this->pdo),
            new PdoProfileRepository($this->pdo)
        );
        $messageService = new MessageService(
            new PdoMessageRepository($this->pdo)
        );
        $userService = new UserService(
            new PdoUserRepository($this->pdo)
        );
        $auditLogService = new AuditLogService(
            new PdoPlatformAuditRepository($this->pdo)
        );
        $authService = new AuthService(
            new PdoUserRepository($this->pdo),
            new PdoRefreshTokenRepository($this->pdo),
            new PdoPasswordResetRepository($this->pdo),
            new PdoPlatformAuditRepository($this->pdo),
            null,
        );

        $securityAction = new SecurityAction(
            $roleService,
            $menuService,
            $messageService,
            $userService,
            $auditLogService,
            $authService,
        );

        $uuid = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';
        $app->group('', function ($g) use ($securityAction, $uuid): void {
            SecurityRoutes::register($g, $securityAction, $uuid);
        });

        $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);
    }

    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",
                is_active INTEGER NOT NULL DEFAULT 1,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                updated_at TEXT NULL
            )',
        );

        $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.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,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP
            )',
        );

        $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.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)
            )',
        );

        $this->pdo->exec(
            'CREATE TABLE auth.messages (
                id TEXT PRIMARY KEY,
                code TEXT NOT NULL UNIQUE,
                subject_es TEXT NOT NULL,
                subject_en TEXT NOT NULL,
                body_html_es TEXT NOT NULL,
                body_html_en TEXT NOT NULL,
                is_active INTEGER NOT NULL DEFAULT 1,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                updated_at TEXT DEFAULT CURRENT_TIMESTAMP
            )',
        );

        $this->pdo->exec(
            'CREATE TABLE companies (
                id TEXT PRIMARY KEY,
                legal_name TEXT NOT NULL,
                trade_name TEXT,
                tax_id TEXT NOT NULL UNIQUE,
                base_currency TEXT NOT NULL DEFAULT "USD",
                email TEXT,
                phone TEXT,
                address 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 user_access (
                id TEXT PRIMARY KEY,
                user_id TEXT NOT NULL,
                company_id TEXT NOT NULL,
                branch_id TEXT,
                can_manage INTEGER NOT NULL DEFAULT 0,
                is_default INTEGER NOT NULL DEFAULT 0,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                UNIQUE (user_id, company_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, is_active) VALUES (?,?,?,?,1)'
        )->execute([self::ADMIN_USER_ID, 'Admin User', 'admin@example.com', $hash]);

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

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

        $this->pdo->prepare(
            'INSERT INTO auth.roles (id, name_es, name_en, description_es, description_en) VALUES (?,?,?,?,?)'
        )->execute([self::MANAGER_ROLE_ID, 'MANAGER', 'MANAGER', 'Manager role', 'Manager role']);

        $this->pdo->prepare(
            'INSERT INTO auth.roles (id, name_es, name_en, description_es, description_en) VALUES (?,?,?,?,?)'
        )->execute([self::CLIENT_ROLE_ID, 'CLIENTE', 'CLIENTE', 'Client role', 'Client role']);

        $this->pdo->prepare('INSERT INTO auth.role_user (role_id, user_id) VALUES (?,?)')
            ->execute([self::ADMIN_ROLE_ID, self::ADMIN_USER_ID]);

        $this->pdo->prepare('INSERT INTO auth.role_user (role_id, user_id) VALUES (?,?)')
            ->execute([self::MANAGER_ROLE_ID, self::MANAGER_USER_ID]);

        // Módulo de configuración
        $this->pdo->prepare(
            'INSERT INTO auth.module (id, name_es, name_en, icon, order_index) VALUES (?,?,?,?,?)'
        )->execute([self::CONFIG_MODULE_ID, 'Configuración', 'Configuration', 'settings', 10]);

        // Página de usuarios
        $this->pdo->prepare(
            'INSERT INTO auth.page (id, module_id, name_es, name_en, path, icon, order_index) VALUES (?,?,?,?,?,?,?)'
        )->execute([self::USERS_PAGE_ID, self::CONFIG_MODULE_ID, 'Usuarios', 'Users', '/security/users', 'people', 1]);

        // Asignar permiso de visualización al administrador para la página de usuarios
        $this->pdo->prepare(
            'INSERT INTO auth.profile (id, role_id, page_id, can_view, can_create, can_edit, can_delete) VALUES (?,?,?,1,1,1,1)'
        )->execute([self::PROFILE_ID, self::ADMIN_ROLE_ID, self::USERS_PAGE_ID]);
    }

    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'] = '';
    }
}
