<?php

declare(strict_types=1);

namespace ApiModel\Tests\Integration\Http;

use ApiModel\Action\CurrencyAction;
use ApiModel\Infrastructure\Persistence\Repositories\PdoCurrencyRepository;
use ApiModel\Route\CurrencyRoutes;
use ApiModel\Service\CurrencyService;
use ApiModel\Middleware\ApiErrorHandler;
use Firebase\JWT\JWT;
use PDO;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Psr7\Factory\ServerRequestFactory;
use Slim\Psr7\Factory\StreamFactory;

final class CurrencyRoutesTest extends TestCase
{
    private PDO $pdo;
    private App $app;
    private string $adminToken;

    private const ADMIN_USER_ID = '11111111-1111-1111-1111-111111111111';

    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->configureEnv();

        $this->app = $this->buildApp();
        $this->adminToken = $this->issueToken(self::ADMIN_USER_ID, 'ADMINISTRADOR');
    }

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

        $this->assertSame(200, $response->getStatusCode());
        $body = $this->decodeBody($response);
        $this->assertIsArray($body);
        $this->assertCount(0, $body);
    }

    public function testCreateAndListCurrencySuccess(): void
    {
        $createResponse = $this->jsonRequest('POST', '/currencies', [
            'code' => 'cop',
            'name' => 'Peso colombiano',
            'symbol' => '$',
            'decimal_places' => 2,
        ], [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);

        $this->assertSame(200, $createResponse->getStatusCode());
        $created = $this->decodeBody($createResponse);
        $this->assertSame('COP', $created['code']);
        $this->assertSame('Peso colombiano', $created['name']);

        $listResponse = $this->request('GET', '/currencies?active=1', [
            'Authorization' => 'Bearer ' . $this->adminToken,
        ]);
        $listBody = $this->decodeBody($listResponse);
        $this->assertCount(1, $listBody);
        $this->assertTrue($listBody[0]['is_active']);
    }

    private function buildApp(): App
    {
        $app = AppFactory::create();
        $currencyAction = new CurrencyAction(new CurrencyService(new PdoCurrencyRepository($this->pdo)));
        $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 ($currencyAction, $uuid): void {
            CurrencyRoutes::register($g, $currencyAction, $uuid);
        });

        $app->addRoutingMiddleware();
        $errorMiddleware = $app->addErrorMiddleware(false, true, true);
        $errorMiddleware->setDefaultErrorHandler(ApiErrorHandler::create($app->getResponseFactory()));

        return $app;
    }

    private function request(string $method, string $path, array $headers = []): 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 = []): 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(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(
            'CREATE TABLE currencies (
                id TEXT PRIMARY KEY,
                code TEXT NOT NULL UNIQUE,
                name TEXT NOT NULL,
                symbol TEXT,
                decimal_places INTEGER NOT NULL DEFAULT 2,
                is_active INTEGER NOT NULL DEFAULT 1,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                updated_at TEXT DEFAULT CURRENT_TIMESTAMP
            )',
        );
    }

    private function configureEnv(): void
    {
        $_ENV['JWT_SECRET'] = 'test-jwt-secret';
    }
}
