<?php

declare(strict_types=1);

namespace ApiModel\Tests\Integration\Http;

use ApiModel\Action\SalesAdjustmentAction;
use ApiModel\Infrastructure\Persistence\Repositories\PdoSalesAdjustmentRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoInvoiceRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoJournalRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoChartAccountRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoTaxRateRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoCompanyRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoBranchRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoExchangeRateRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoUserAccessRepository;
use ApiModel\Infrastructure\Persistence\Repositories\PdoProfileRepository;
use ApiModel\Middleware\CompanyContextMiddleware;
use ApiModel\Middleware\ApiErrorHandler;
use ApiModel\Route\SalesAdjustmentRoutes;
use ApiModel\Service\SalesAdjustmentService;
use ApiModel\Service\InvoiceService;
use ApiModel\Service\JournalService;
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 SalesAdjustmentRoutesTest extends TestCase
{
    private PDO $pdo;
    private App $app;
    private string $adminToken;

    private const ADMIN_USER_ID = '11111111-1111-1111-1111-111111111111';
    private const ADMIN_ROLE_ID = '33333333-3333-3333-3333-333333333333';
    private const COMPANY_ID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
    private const BRANCH_ID = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';

    private const RECEIVABLE_ACCOUNT_ID = '11050000-0000-0000-0000-000000000001';
    private const REVENUE_ACCOUNT_ID = '41050000-0000-0000-0000-000000000002';
    private const TAX_ACCOUNT_ID = '24050000-0000-0000-0000-000000000003';
    private const DISCOUNT_ACCOUNT_ID = '52050000-0000-0000-0000-000000000004';
    
    private const CUSTOMER_ID = 'dddddddd-dddd-dddd-dddd-dddddddddddd';
    private const TAX_RATE_ID = 'tttttttt-tttt-tttt-tttt-tttttttttttt';

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

    private function companyHeaders(): array
    {
        return [
            'Authorization' => 'Bearer ' . $this->adminToken,
            'X-Company-Id' => self::COMPANY_ID,
        ];
    }

    public function testCreditNoteCrudAndAutoPosting(): void
    {
        // 1. List adjustments initially (should be empty)
        $res = $this->request('GET', '/sales/adjustments?type=credit_note', $this->companyHeaders());
        $this->assertSame(200, $res->getStatusCode());
        $this->assertCount(0, $this->decodeBody($res));

        // 2. Create a Sales Invoice to reference
        // First we insert a posted sales invoice
        $invoiceId = 'face0000-0000-0000-0000-000000000000';
        $invoiceObj = [
            'id' => $invoiceId,
            'company_id' => self::COMPANY_ID,
            'branch_id' => self::BRANCH_ID,
            'customer_id' => self::CUSTOMER_ID,
            'receivable_account_id' => self::RECEIVABLE_ACCOUNT_ID,
            'invoice_number' => 'FC-000001',
            'invoice_date' => '2026-06-01',
            'due_date' => '2026-06-30',
            'reference' => 'FC-REF-01',
            'currency_code' => 'USD',
            'exchange_rate' => 1.0,
            'discount_amount' => 0.0,
            'tax_amount' => 150.0, // 15% on 1000
            'subtotal' => 1000.0,
            'total' => 1150.0,
            'balance' => 1150.0,
            'status' => 'posted',
            'notes' => 'Invoice Demo',
            'created_by' => self::ADMIN_USER_ID
        ];
        
        // Insert invoice and its line
        $this->pdo->prepare('INSERT INTO sales_invoices (id, company_id, branch_id, customer_id, receivable_account_id, invoice_number, invoice_date, due_date, reference, currency_code, exchange_rate, discount_amount, tax_amount, subtotal, total, balance, status, notes, created_by) VALUES (:id, :company_id, :branch_id, :customer_id, :receivable_account_id, :invoice_number, :invoice_date, :due_date, :reference, :currency_code, :exchange_rate, :discount_amount, :tax_amount, :subtotal, :total, :balance, :status, :notes, :created_by)')->execute($invoiceObj);
        $this->pdo->prepare('INSERT INTO sales_invoice_lines (id, invoice_id, description, quantity, price, tax_rate_id, tax_amount, total, chart_account_id, line_order) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0)')
            ->execute(['line0000-0000-0000-0000-000000000000', $invoiceId, 'Item Demo', 10.0, 100.0, self::TAX_RATE_ID, 150.0, 1150.0, self::REVENUE_ACCOUNT_ID]);

        // 3. Create Draft Credit Note
        $payload = [
            'adjustment_type' => 'credit_note',
            'customer_id' => self::CUSTOMER_ID,
            'receivable_account_id' => self::RECEIVABLE_ACCOUNT_ID,
            'document_date' => '2026-06-10',
            'reference_invoice_id' => $invoiceId,
            'status' => 'draft',
            'notes' => 'Credit note for return',
            'lines' => [
                [
                    'description' => 'Return 2 items',
                    'quantity' => 2.0,
                    'price' => 100.0,
                    'tax_rate_id' => self::TAX_RATE_ID,
                    'chart_account_id' => self::REVENUE_ACCOUNT_ID
                ]
            ]
        ];

        $res = $this->jsonRequest('POST', '/sales/adjustments', $payload, $this->companyHeaders());
        $this->assertSame(200, $res->getStatusCode());
        $note = $this->decodeBody($res);
        $this->assertArrayHasKey('id', $note);
        $this->assertSame('draft', $note['status']);
        $this->assertEquals(200.0, $note['subtotal']);
        $this->assertEquals(30.0, $note['tax_amount']); // 15% on 200
        $this->assertEquals(230.0, $note['total']);

        // Check reference invoice balance remains unchanged while credit note is draft
        $invoiceRow = $this->pdo->query('SELECT balance FROM sales_invoices WHERE id = \'' . $invoiceId . '\'')->fetch();
        $this->assertEquals(1150.0, (float)$invoiceRow['balance']);

        // 4. Update Credit Note Notes and Status to posted
        $payload['id'] = $note['id'];
        $payload['notes'] = 'Credit Note Approved';
        $payload['status'] = 'posted';

        $res = $this->jsonRequest('PUT', '/sales/adjustments/' . $note['id'], $payload, $this->companyHeaders());
        $this->assertSame(200, $res->getStatusCode());
        $notePosted = $this->decodeBody($res);
        $this->assertSame('posted', $notePosted['status']);
        $this->assertNotNull($notePosted['journal_entry_id']);

        // Check reference invoice balance is reduced: 1150 - 230 = 920
        $invoiceRow = $this->pdo->query('SELECT balance, status FROM sales_invoices WHERE id = \'' . $invoiceId . '\'')->fetch();
        $this->assertEquals(920.0, (float)$invoiceRow['balance']);
        $this->assertSame('partially_paid', $invoiceRow['status']);

        // Check journal entries was posted (receivables credit, revenue debit, tax debit)
        $jeId = $notePosted['journal_entry_id'];
        $jeLines = $this->pdo->query("SELECT * FROM journal_lines WHERE journal_entry_id = '{$jeId}' ORDER BY line_order ASC")->fetchAll();
        $this->assertCount(3, $jeLines);
        
        // Line 0: Revenue Debit (200.0)
        $this->assertSame(self::REVENUE_ACCOUNT_ID, $jeLines[0]['chart_account_id']);
        $this->assertEquals(200.0, (float)$jeLines[0]['debit']);
        $this->assertEquals(0.0, (float)$jeLines[0]['credit']);

        // Line 1: Tax Debit (30.0)
        $this->assertSame(self::TAX_ACCOUNT_ID, $jeLines[1]['chart_account_id']);
        $this->assertEquals(30.0, (float)$jeLines[1]['debit']);
        $this->assertEquals(0.0, (float)$jeLines[1]['credit']);

        // Line 2: Receivables Credit (230.0)
        $this->assertSame(self::RECEIVABLE_ACCOUNT_ID, $jeLines[2]['chart_account_id']);
        $this->assertEquals(0.0, (float)$jeLines[2]['debit']);
        $this->assertEquals(230.0, (float)$jeLines[2]['credit']);

        // 5. Try to create another Credit Note exceeding remaining balance (920)
        $payloadExcess = [
            'adjustment_type' => 'credit_note',
            'customer_id' => self::CUSTOMER_ID,
            'receivable_account_id' => self::RECEIVABLE_ACCOUNT_ID,
            'document_date' => '2026-06-12',
            'reference_invoice_id' => $invoiceId,
            'status' => 'posted',
            'lines' => [
                [
                    'description' => 'Exceeding refund',
                    'quantity' => 1.0,
                    'price' => 1000.0, // total 1000 exceeds 920
                    'chart_account_id' => self::REVENUE_ACCOUNT_ID
                ]
            ]
        ];
        $resExcess = $this->jsonRequest('POST', '/sales/adjustments', $payloadExcess, $this->companyHeaders());
        $this->assertSame(400, $resExcess->getStatusCode());

        // 6. Delete Credit Note
        $resDelete = $this->request('DELETE', '/sales/adjustments/' . $note['id'], $this->companyHeaders());
        $this->assertSame(200, $resDelete->getStatusCode());

        // Verify reference invoice balance is restored to 1150
        $invoiceRow = $this->pdo->query('SELECT balance, status FROM sales_invoices WHERE id = \'' . $invoiceId . '\'')->fetch();
        $this->assertEquals(1150.0, (float)$invoiceRow['balance']);
        $this->assertSame('posted', $invoiceRow['status']);

        // Verify Journal Entry was deleted
        $jeCount = $this->pdo->query("SELECT COUNT(*) FROM journal_entries WHERE id = '{$jeId}'")->fetchColumn();
        $this->assertEquals(0, $jeCount);
    }

    public function testDebitNoteCrudAndAutoPosting(): void
    {
        // 1. Create a Sales Invoice
        $invoiceId = 'face0000-0000-0000-0000-000000000001';
        $invoiceObj = [
            'id' => $invoiceId,
            'company_id' => self::COMPANY_ID,
            'branch_id' => self::BRANCH_ID,
            'customer_id' => self::CUSTOMER_ID,
            'receivable_account_id' => self::RECEIVABLE_ACCOUNT_ID,
            'invoice_number' => 'FC-000002',
            'invoice_date' => '2026-06-01',
            'due_date' => '2026-06-30',
            'reference' => 'FC-REF-02',
            'currency_code' => 'USD',
            'exchange_rate' => 1.0,
            'discount_amount' => 0.0,
            'tax_amount' => 0.0,
            'subtotal' => 1000.0,
            'total' => 1000.0,
            'balance' => 1000.0,
            'status' => 'posted',
            'notes' => 'Invoice Demo 2',
            'created_by' => self::ADMIN_USER_ID
        ];
        
        $this->pdo->prepare('INSERT INTO sales_invoices (id, company_id, branch_id, customer_id, receivable_account_id, invoice_number, invoice_date, due_date, reference, currency_code, exchange_rate, discount_amount, tax_amount, subtotal, total, balance, status, notes, created_by) VALUES (:id, :company_id, :branch_id, :customer_id, :receivable_account_id, :invoice_number, :invoice_date, :due_date, :reference, :currency_code, :exchange_rate, :discount_amount, :tax_amount, :subtotal, :total, :balance, :status, :notes, :created_by)')->execute($invoiceObj);

        // 2. Create Debit Note (posted)
        $payload = [
            'adjustment_type' => 'debit_note',
            'customer_id' => self::CUSTOMER_ID,
            'receivable_account_id' => self::RECEIVABLE_ACCOUNT_ID,
            'document_date' => '2026-06-15',
            'reference_invoice_id' => $invoiceId,
            'status' => 'posted',
            'notes' => 'Debit note for logistics',
            'lines' => [
                [
                    'description' => 'Late delivery fee',
                    'quantity' => 1.0,
                    'price' => 150.0,
                    'chart_account_id' => self::REVENUE_ACCOUNT_ID
                ]
            ]
        ];

        $res = $this->jsonRequest('POST', '/sales/adjustments', $payload, $this->companyHeaders());
        $this->assertSame(200, $res->getStatusCode());
        $note = $this->decodeBody($res);

        // Verify balance increased: 1000 + 150 = 1150
        $invoiceRow = $this->pdo->query('SELECT balance, status FROM sales_invoices WHERE id = \'' . $invoiceId . '\'')->fetch();
        $this->assertEquals(1150.0, (float)$invoiceRow['balance']);
        $this->assertSame('partially_paid', $invoiceRow['status']);

        // Check journal entries (receivables debit, revenue credit)
        $jeId = $note['journal_entry_id'];
        $jeLines = $this->pdo->query("SELECT * FROM journal_lines WHERE journal_entry_id = '{$jeId}' ORDER BY line_order ASC")->fetchAll();
        $this->assertCount(2, $jeLines);
        
        // Receivables Debit (150.0)
        $this->assertSame(self::RECEIVABLE_ACCOUNT_ID, $jeLines[0]['chart_account_id']);
        $this->assertEquals(150.0, (float)$jeLines[0]['debit']);

        // Revenue Credit (150.0)
        $this->assertSame(self::REVENUE_ACCOUNT_ID, $jeLines[1]['chart_account_id']);
        $this->assertEquals(150.0, (float)$jeLines[1]['credit']);

        // 3. Delete Debit Note
        $resDelete = $this->request('DELETE', '/sales/adjustments/' . $note['id'], $this->companyHeaders());
        $this->assertSame(200, $resDelete->getStatusCode());

        // Verify balance is reverted back to 1000
        $invoiceRow = $this->pdo->query('SELECT balance, status FROM sales_invoices WHERE id = \'' . $invoiceId . '\'')->fetch();
        $this->assertEquals(1000.0, (float)$invoiceRow['balance']);
        $this->assertSame('posted', $invoiceRow['status']);
    }

    private function createSchema(): void
    {
        $this->pdo->exec('ATTACH DATABASE \':memory:\' AS auth');
        $this->pdo->exec('CREATE TABLE auth.users (id TEXT PRIMARY KEY, name TEXT, email TEXT, password_hash TEXT, is_active INTEGER)');
        $this->pdo->exec('CREATE TABLE auth.roles (id TEXT PRIMARY KEY, name_es TEXT, name_en TEXT)');
        $this->pdo->exec('CREATE TABLE auth.role_user (role_id TEXT, user_id TEXT)');
        
        $this->pdo->exec(
            'CREATE TABLE companies (
                id TEXT PRIMARY KEY,
                legal_name TEXT NOT NULL,
                trade_name TEXT,
                tax_id TEXT NOT NULL,
                base_currency TEXT NOT NULL DEFAULT "USD",
                is_active INTEGER NOT NULL DEFAULT 1
            )'
        );

        $this->pdo->exec(
            'CREATE TABLE branches (
                id TEXT PRIMARY KEY,
                company_id TEXT NOT NULL,
                name TEXT NOT NULL,
                code TEXT NOT NULL,
                is_main INTEGER NOT NULL DEFAULT 0,
                operating_currency TEXT NOT NULL DEFAULT "USD",
                is_active INTEGER NOT NULL DEFAULT 1
            )'
        );

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

        $this->pdo->exec(
            'CREATE TABLE chart_accounts (
                id TEXT PRIMARY KEY,
                company_id TEXT NOT NULL,
                code TEXT NOT NULL,
                name TEXT NOT NULL,
                account_type TEXT NOT NULL,
                parent_id TEXT,
                is_header INTEGER NOT NULL DEFAULT 0,
                is_active INTEGER NOT NULL DEFAULT 1
            )'
        );

        $this->pdo->exec(
            'CREATE TABLE tax_rates (
                id TEXT PRIMARY KEY,
                company_id TEXT NOT NULL,
                name TEXT NOT NULL,
                rate REAL NOT NULL DEFAULT 0.0,
                type TEXT NOT NULL DEFAULT "sale",
                is_active INTEGER NOT NULL DEFAULT 1
            )'
        );

        $this->pdo->exec(
            'CREATE TABLE contacts (
                id TEXT PRIMARY KEY,
                company_id TEXT NOT NULL,
                type TEXT NOT NULL,
                name TEXT NOT NULL,
                tax_id TEXT,
                email TEXT,
                phone TEXT,
                address TEXT,
                is_active INTEGER NOT NULL DEFAULT 1
            )'
        );

        $this->pdo->exec(
            'CREATE TABLE sales_invoices (
                id TEXT PRIMARY KEY,
                company_id TEXT NOT NULL,
                branch_id TEXT,
                customer_id TEXT NOT NULL,
                receivable_account_id TEXT NOT NULL,
                invoice_number TEXT NOT NULL,
                invoice_date TEXT NOT NULL,
                due_date TEXT NOT NULL,
                reference TEXT,
                currency_code TEXT NOT NULL DEFAULT "USD",
                exchange_rate REAL NOT NULL DEFAULT 1.0,
                discount_amount REAL NOT NULL DEFAULT 0.0,
                tax_amount REAL NOT NULL DEFAULT 0.0,
                subtotal REAL NOT NULL DEFAULT 0.0,
                total REAL NOT NULL DEFAULT 0.0,
                balance REAL NOT NULL DEFAULT 0.0,
                status TEXT NOT NULL DEFAULT "draft",
                notes TEXT,
                created_by TEXT,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                updated_at TEXT DEFAULT CURRENT_TIMESTAMP
            )'
        );

        $this->pdo->exec(
            'CREATE TABLE sales_invoice_lines (
                id TEXT PRIMARY KEY,
                invoice_id TEXT NOT NULL,
                description TEXT,
                quantity REAL NOT NULL DEFAULT 1.0,
                price REAL NOT NULL DEFAULT 0.0,
                tax_rate_id TEXT,
                tax_amount REAL NOT NULL DEFAULT 0.0,
                total REAL NOT NULL DEFAULT 0.0,
                chart_account_id TEXT NOT NULL,
                line_order INTEGER NOT NULL DEFAULT 0
            )'
        );

        $this->pdo->exec(
            'CREATE TABLE journal_entries (
                id TEXT PRIMARY KEY,
                company_id TEXT NOT NULL,
                branch_id TEXT,
                entry_number TEXT NOT NULL,
                entry_date TEXT NOT NULL,
                description TEXT,
                status TEXT NOT NULL DEFAULT "draft",
                operating_currency TEXT NOT NULL DEFAULT "USD",
                functional_currency TEXT NOT NULL DEFAULT "USD",
                exchange_rate REAL NOT NULL DEFAULT 1.0,
                created_by TEXT,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                updated_at TEXT DEFAULT CURRENT_TIMESTAMP
            )'
        );

        $this->pdo->exec(
            'CREATE TABLE journal_lines (
                id TEXT PRIMARY KEY,
                journal_entry_id TEXT NOT NULL,
                chart_account_id TEXT NOT NULL,
                description TEXT,
                debit REAL NOT NULL DEFAULT 0,
                credit REAL NOT NULL DEFAULT 0,
                debit_functional REAL NOT NULL DEFAULT 0,
                credit_functional REAL NOT NULL DEFAULT 0,
                line_order INTEGER NOT NULL DEFAULT 0
            )'
        );

        $this->pdo->exec(
            'CREATE TABLE sales_adjustments (
                id TEXT PRIMARY KEY,
                company_id TEXT NOT NULL,
                branch_id TEXT,
                customer_id TEXT NOT NULL,
                adjustment_type TEXT NOT NULL,
                document_number TEXT NOT NULL,
                document_date TEXT NOT NULL,
                reference_invoice_id TEXT,
                receivable_account_id TEXT NOT NULL,
                discount_amount REAL NOT NULL DEFAULT 0.0,
                tax_amount REAL NOT NULL DEFAULT 0.0,
                subtotal REAL NOT NULL DEFAULT 0.0,
                total REAL NOT NULL DEFAULT 0.0,
                status TEXT NOT NULL DEFAULT "draft",
                journal_entry_id TEXT,
                notes TEXT,
                created_by TEXT,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                updated_at TEXT DEFAULT CURRENT_TIMESTAMP
            )'
        );

        $this->pdo->exec(
            'CREATE TABLE sales_adjustment_lines (
                id TEXT PRIMARY KEY,
                adjustment_id TEXT NOT NULL,
                description TEXT,
                quantity REAL NOT NULL DEFAULT 1.0,
                price REAL NOT NULL DEFAULT 0.0,
                tax_rate_id TEXT,
                tax_amount REAL NOT NULL DEFAULT 0.0,
                total REAL NOT NULL DEFAULT 0.0,
                chart_account_id TEXT NOT NULL,
                line_order INTEGER NOT NULL DEFAULT 0
            )'
        );
    }

    private function seedData(): void
    {
        $this->pdo->prepare(
            'INSERT INTO auth.users (id, name, email, password_hash, is_active) VALUES (?,?,?, "hash", 1)'
        )->execute([self::ADMIN_USER_ID, 'Admin User', 'admin@example.com']);

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

        $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 companies (id, legal_name, trade_name, tax_id, base_currency, is_active)
             VALUES (?,?,?,?,?,1)'
        )->execute([self::COMPANY_ID, 'Empresa Demo', 'Demo', '900111222-3', 'USD']);

        $this->pdo->prepare(
            'INSERT INTO branches (id, company_id, name, code, is_main, operating_currency, is_active)
             VALUES (?,?,?,?,1,?,1)'
        )->execute([self::BRANCH_ID, self::COMPANY_ID, 'Principal', 'MAIN', 'USD']);

        $this->pdo->prepare(
            'INSERT INTO user_access (id, user_id, company_id, branch_id, can_manage, is_default)
             VALUES (?,?,?,?,1,1)'
        )->execute(['cccccccc-cccc-cccc-cccc-cccccccccccc', self::ADMIN_USER_ID, self::COMPANY_ID, self::BRANCH_ID]);

        // Seed accounts
        $this->pdo->prepare('INSERT INTO chart_accounts (id, company_id, code, name, account_type, is_header) VALUES (?,?,?,?,?,0)')
            ->execute([self::RECEIVABLE_ACCOUNT_ID, self::COMPANY_ID, '1105', 'Cuentas por Cobrar', 'asset']);

        $this->pdo->prepare('INSERT INTO chart_accounts (id, company_id, code, name, account_type, is_header) VALUES (?,?,?,?,?,0)')
            ->execute([self::REVENUE_ACCOUNT_ID, self::COMPANY_ID, '4105', 'Ventas', 'revenue']);

        $this->pdo->prepare('INSERT INTO chart_accounts (id, company_id, code, name, account_type, is_header) VALUES (?,?,?,?,?,0)')
            ->execute([self::TAX_ACCOUNT_ID, self::COMPANY_ID, '2405', 'Impuestos', 'liability']);

        $this->pdo->prepare('INSERT INTO chart_accounts (id, company_id, code, name, account_type, is_header) VALUES (?,?,?,?,?,0)')
            ->execute([self::DISCOUNT_ACCOUNT_ID, self::COMPANY_ID, '5205', 'Descuentos', 'expense']);

        // Seed Customer
        $this->pdo->prepare('INSERT INTO contacts (id, company_id, type, name, tax_id, is_active) VALUES (?, ?, ?, ?, ?, 1)')
            ->execute([self::CUSTOMER_ID, self::COMPANY_ID, 'customer', 'Cliente Demo', '12345678-9']);

        // Seed Tax Rate
        $this->pdo->prepare('INSERT INTO tax_rates (id, company_id, name, rate, type, is_active) VALUES (?, ?, ?, ?, ?, 1)')
            ->execute([self::TAX_RATE_ID, self::COMPANY_ID, 'IVA 15%', 15.0, 'sale']);
    }

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

    private function buildApp(): App
    {
        $app = AppFactory::create();
        $app->addBodyParsingMiddleware();
        $app->addRoutingMiddleware();

        $companyMiddleware = new CompanyContextMiddleware(new \ApiModel\Service\OrganizationService(
            new PdoCompanyRepository($this->pdo),
            new PdoBranchRepository($this->pdo),
            new PdoUserAccessRepository($this->pdo),
            new PdoProfileRepository($this->pdo)
        ));
        $app->add($companyMiddleware);

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

        $action = new SalesAdjustmentAction(new SalesAdjustmentService(
            new PdoSalesAdjustmentRepository($this->pdo),
            new PdoInvoiceRepository($this->pdo),
            new PdoJournalRepository($this->pdo),
            new PdoTaxRateRepository($this->pdo),
            new JournalService(
                new PdoJournalRepository($this->pdo),
                new PdoChartAccountRepository($this->pdo),
                new PdoCompanyRepository($this->pdo),
                new PdoBranchRepository($this->pdo),
                new PdoExchangeRateRepository($this->pdo)
            ),
            $this->pdo
        ));

        SalesAdjustmentRoutes::register($app, $action, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}');

        return $app;
    }

    private function issueToken(string $userId, string $role): string
    {
        $payload = [
            'sub' => $userId,
            'email' => 'admin@example.com',
            'role' => $role,
            'lang' => 'es',
            'iat' => time(),
            'exp' => time() + 3600,
        ];

        return JWT::encode($payload, 'test-jwt-secret', 'HS256');
    }

    private function request(string $method, string $path, array $headers = [], ?string $body = null): ResponseInterface
    {
        $req = (new ServerRequestFactory())->createServerRequest($method, $path);

        foreach ($headers as $k => $v) {
            $req = $req->withHeader($k, $v);
        }

        if ($body !== null) {
            $stream = (new StreamFactory())->createStream($body);
            $req = $req->withBody($stream);
        }

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

    private function jsonRequest(string $method, string $path, array $payload, array $headers = []): ResponseInterface
    {
        $headers['Content-Type'] = 'application/json';
        return $this->request($method, $path, $headers, json_encode($payload));
    }

    private function decodeBody(ResponseInterface $res): array
    {
        $body = (string) $res->getBody();
        $data = json_decode($body, true);
        return $data['data'] ?? $data;
    }
}
