<?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;

final class TokensControllerTest extends AppTestCase
{
    public function testCreateReporterTokenReturnsRawOnce(): void
    {
        $admin = $this->createToken(TokenKind::Admin, role: Role::Admin);
        $reporterId = $this->createReporter('web-tokens');

        $resp = $this->request(
            'POST',
            '/api/v1/admin/tokens',
            ['Authorization' => 'Bearer ' . $admin, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'reporter', 'reporter_id' => $reporterId]) ?: null,
        );
        self::assertSame(201, $resp->getStatusCode());

        $body = $this->decode($resp);
        self::assertSame('reporter', $body['kind']);
        self::assertSame($reporterId, $body['reporter_id']);
        self::assertArrayHasKey('raw_token', $body);
        self::assertStringStartsWith('irdb_rep_', (string) $body['raw_token']);

        // Second fetch via list must not include raw_token.
        $list = $this->request('GET', '/api/v1/admin/tokens', [
            'Authorization' => 'Bearer ' . $admin,
        ]);
        $listBody = $this->decode($list);
        foreach ($listBody['data'] as $row) {
            self::assertArrayNotHasKey('raw_token', $row);
        }
    }

    public function testCreateAdminTokenRequiresRole(): void
    {
        $admin = $this->createToken(TokenKind::Admin, role: Role::Admin);
        $resp = $this->request(
            'POST',
            '/api/v1/admin/tokens',
            ['Authorization' => 'Bearer ' . $admin, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'admin']) ?: null,
        );
        self::assertSame(400, $resp->getStatusCode());
        self::assertArrayHasKey('role', $this->decode($resp)['details']);
    }

    public function testServiceKindRefused(): void
    {
        $admin = $this->createToken(TokenKind::Admin, role: Role::Admin);
        $resp = $this->request(
            'POST',
            '/api/v1/admin/tokens',
            ['Authorization' => 'Bearer ' . $admin, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'service']) ?: null,
        );
        self::assertSame(400, $resp->getStatusCode());
    }

    public function testListExcludesServiceTokens(): void
    {
        $admin = $this->createToken(TokenKind::Admin, role: Role::Admin);
        $this->createToken(TokenKind::Service);

        $resp = $this->request('GET', '/api/v1/admin/tokens', [
            'Authorization' => 'Bearer ' . $admin,
        ]);
        $body = $this->decode($resp);
        foreach ($body['data'] as $row) {
            self::assertNotSame('service', $row['kind']);
        }
    }

    public function testRevokeMarksRevokedAt(): void
    {
        $admin = $this->createToken(TokenKind::Admin, role: Role::Admin);
        $reporterId = $this->createReporter('web-revoke');
        $created = $this->request(
            'POST',
            '/api/v1/admin/tokens',
            ['Authorization' => 'Bearer ' . $admin, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'reporter', 'reporter_id' => $reporterId]) ?: null,
        );
        $tokenId = (int) $this->decode($created)['id'];

        $delete = $this->request('DELETE', "/api/v1/admin/tokens/{$tokenId}", [
            'Authorization' => 'Bearer ' . $admin,
        ]);
        self::assertSame(204, $delete->getStatusCode());

        $row = $this->db->fetchAssociative('SELECT revoked_at FROM api_tokens WHERE id = :id', ['id' => $tokenId]);
        self::assertIsArray($row);
        self::assertNotNull($row['revoked_at']);
    }

    public function testPurgeDeletesRevokedToken(): void
    {
        $admin = $this->createToken(TokenKind::Admin, role: Role::Admin);
        $reporterId = $this->createReporter('web-purge');
        $created = $this->request(
            'POST',
            '/api/v1/admin/tokens',
            ['Authorization' => 'Bearer ' . $admin, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'reporter', 'reporter_id' => $reporterId]) ?: null,
        );
        $tokenId = (int) $this->decode($created)['id'];

        // Revoke first.
        self::assertSame(204, $this->request('DELETE', "/api/v1/admin/tokens/{$tokenId}", [
            'Authorization' => 'Bearer ' . $admin,
        ])->getStatusCode());

        $purge = $this->request('DELETE', "/api/v1/admin/tokens/{$tokenId}/purge", [
            'Authorization' => 'Bearer ' . $admin,
        ]);
        self::assertSame(204, $purge->getStatusCode());

        $count = (int) $this->db->fetchOne('SELECT COUNT(*) FROM api_tokens WHERE id = :id', ['id' => $tokenId]);
        self::assertSame(0, $count);

        $audit = $this->db->fetchAssociative(
            "SELECT action, target_id FROM audit_log WHERE action = 'token.deleted' ORDER BY id DESC LIMIT 1"
        );
        self::assertIsArray($audit);
        self::assertSame((string) $tokenId, $audit['target_id']);
    }

    public function testPurgeRefusesActiveToken(): void
    {
        $admin = $this->createToken(TokenKind::Admin, role: Role::Admin);
        $reporterId = $this->createReporter('web-still-active');
        $created = $this->request(
            'POST',
            '/api/v1/admin/tokens',
            ['Authorization' => 'Bearer ' . $admin, 'Content-Type' => 'application/json'],
            json_encode(['kind' => 'reporter', 'reporter_id' => $reporterId]) ?: null,
        );
        $tokenId = (int) $this->decode($created)['id'];

        $purge = $this->request('DELETE', "/api/v1/admin/tokens/{$tokenId}/purge", [
            'Authorization' => 'Bearer ' . $admin,
        ]);
        self::assertSame(409, $purge->getStatusCode());
        self::assertSame('not_revoked', $this->decode($purge)['error']);

        $count = (int) $this->db->fetchOne('SELECT COUNT(*) FROM api_tokens WHERE id = :id', ['id' => $tokenId]);
        self::assertSame(1, $count);
    }
}
