<?php

declare(strict_types=1);

namespace App\Tests\Integration\Admin;

use App\Domain\Auth\Role;
use App\Domain\Auth\TokenKind;
use App\Tests\Integration\Support\AppTestCase;

/**
 * `/api/v1/admin/maintenance/{purge,seed-demo}` — admin-only one-shot
 * data tools used by the Settings page for demos and resets.
 */
final class MaintenanceControllerTest extends AppTestCase
{
    public function testPurgeRequiresAdmin(): void
    {
        $token = $this->createToken(TokenKind::Admin, Role::Operator);
        $resp = $this->request(
            'POST',
            '/api/v1/admin/maintenance/purge',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            '{"confirm": "PURGE"}',
        );
        self::assertSame(403, $resp->getStatusCode());
    }

    public function testPurgeRequiresLiteralConfirmString(): void
    {
        $token = $this->createToken(TokenKind::Admin, Role::Admin);
        $resp = $this->request(
            'POST',
            '/api/v1/admin/maintenance/purge',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            '{"confirm": "yes"}',
        );
        self::assertSame(400, $resp->getStatusCode());
        self::assertSame('validation_failed', $this->decode($resp)['error']);
    }

    public function testPurgeWipesOperationalDataButPreservesServiceToken(): void
    {
        // Seed a service token (bypassing the controller; the API protects it).
        $this->db->insert('api_tokens', [
            'kind' => TokenKind::Service->value,
            'token_hash' => str_repeat('a', 64),
            'token_prefix' => 'irdb_svc',
        ]);
        // And one operational token, plus a reporter and a stray report.
        $reporterId = $this->createReporter('purge-victim');
        $catRow = $this->db->fetchAssociative('SELECT id FROM categories LIMIT 1');
        self::assertNotFalse($catRow);
        $this->db->insert('reports', [
            'ip_bin' => str_repeat("\x00", 12) . "\x00\x00\x00\x01",
            'ip_text' => '0.0.0.1',
            'category_id' => (int) $catRow['id'],
            'reporter_id' => $reporterId,
            'weight_at_report' => '1.00',
        ]);

        $token = $this->createToken(TokenKind::Admin, Role::Admin);
        $resp = $this->request(
            'POST',
            '/api/v1/admin/maintenance/purge',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            '{"confirm": "PURGE"}',
        );
        self::assertSame(200, $resp->getStatusCode());
        $body = $this->decode($resp);
        self::assertSame('purged', $body['status']);
        self::assertGreaterThanOrEqual(1, (int) $body['deleted']['reports']);

        // Service token survives, the admin token does not.
        $svcCount = (int) $this->db->fetchOne(
            'SELECT COUNT(*) FROM api_tokens WHERE kind = :k',
            ['k' => TokenKind::Service->value],
        );
        self::assertSame(1, $svcCount);
        $allTokens = (int) $this->db->fetchOne('SELECT COUNT(*) FROM api_tokens');
        self::assertSame(1, $allTokens, 'only the service token should remain');

        // Reporters / reports gone.
        self::assertSame(0, (int) $this->db->fetchOne('SELECT COUNT(*) FROM reporters'));
        self::assertSame(0, (int) $this->db->fetchOne('SELECT COUNT(*) FROM reports'));

        // Categories preserved.
        self::assertGreaterThan(0, (int) $this->db->fetchOne('SELECT COUNT(*) FROM categories'));
    }

    public function testSeedDemoPopulatesDataAndIsIdempotent(): void
    {
        $token = $this->createToken(TokenKind::Admin, Role::Admin);

        $first = $this->request(
            'POST',
            '/api/v1/admin/maintenance/seed-demo',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            '{"confirm": "SEED"}',
        );
        self::assertSame(200, $first->getStatusCode());
        $body = $this->decode($first);
        self::assertSame('seeded', $body['status']);
        self::assertGreaterThan(0, (int) $body['summary']['reporters']);
        self::assertGreaterThan(0, (int) $body['summary']['ips']);
        self::assertGreaterThan(0, (int) $body['summary']['reports']);

        // Real rows landed.
        self::assertGreaterThan(0, (int) $this->db->fetchOne('SELECT COUNT(*) FROM reporters'));
        self::assertGreaterThan(0, (int) $this->db->fetchOne('SELECT COUNT(*) FROM reports'));

        // Second call short-circuits with 409.
        $second = $this->request(
            'POST',
            '/api/v1/admin/maintenance/seed-demo',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            '{"confirm": "SEED"}',
        );
        self::assertSame(409, $second->getStatusCode());
        self::assertSame('already_seeded', $this->decode($second)['error']);
    }

    public function testSeedDemoForbiddenForViewer(): void
    {
        $token = $this->createToken(TokenKind::Admin, Role::Viewer);
        $resp = $this->request(
            'POST',
            '/api/v1/admin/maintenance/seed-demo',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            '{"confirm": "SEED"}',
        );
        self::assertSame(403, $resp->getStatusCode());
    }

    /**
     * SEC_REVIEW F15: a single drive-by POST with no body must not load the
     * demo dataset. The API requires `confirm: "SEED"` symmetric with
     * `purge`'s `"PURGE"` gate.
     */
    public function testSeedDemoRequiresLiteralConfirmString(): void
    {
        $token = $this->createToken(TokenKind::Admin, Role::Admin);

        // No body at all.
        $bare = $this->request(
            'POST',
            '/api/v1/admin/maintenance/seed-demo',
            ['Authorization' => 'Bearer ' . $token],
        );
        self::assertSame(400, $bare->getStatusCode());
        self::assertSame('validation_failed', $this->decode($bare)['error']);

        // Wrong literal.
        $wrong = $this->request(
            'POST',
            '/api/v1/admin/maintenance/seed-demo',
            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
            '{"confirm": "yes"}',
        );
        self::assertSame(400, $wrong->getStatusCode());
        self::assertSame('validation_failed', $this->decode($wrong)['error']);

        // Nothing landed.
        self::assertSame(0, (int) $this->db->fetchOne('SELECT COUNT(*) FROM reporters'));
        self::assertSame(0, (int) $this->db->fetchOne('SELECT COUNT(*) FROM reports'));
    }
}
