<?php

declare(strict_types=1);

namespace Tests\Support\Api;

use PHPUnit\Framework\Assert;

final class ApiResponse
{
    /**
     * @param array<string, string> $headers
     * @param array<string, mixed>|null $json
     */
    public function __construct(
        public readonly int $status,
        public readonly array $headers,
        public readonly ?array $json,
        public readonly string $body,
    ) {
    }

    public function assertStatus(int $expectedStatus): self
    {
        Assert::assertSame($expectedStatus, $this->status, $this->body);

        return $this;
    }

    public function assertEnvelope(): self
    {
        Assert::assertIsArray($this->json, 'Expected a JSON response body. Raw body: ' . $this->body);
        Assert::assertArrayHasKey('success', $this->json);
        Assert::assertArrayHasKey('data', $this->json);
        Assert::assertArrayHasKey('meta', $this->json);
        Assert::assertArrayHasKey('includes', $this->json);
        Assert::assertArrayHasKey('content-type', $this->headers);
        Assert::assertStringContainsString('application/json', strtolower($this->headers['content-type']));

        return $this;
    }

    public function assertSuccess(bool $expected = true): self
    {
        $this->assertEnvelope();
        Assert::assertSame($expected, $this->json['success']);

        return $this;
    }

    public function assertError(): self
    {
        return $this->assertSuccess(false);
    }

    public function assertMessage(string $expectedMessage): self
    {
        $this->assertEnvelope();
        Assert::assertIsArray($this->json['data']);
        Assert::assertSame($expectedMessage, $this->json['data']['message'] ?? null, $this->body);

        return $this;
    }

    public function assertMessageContains(string $expectedFragment): self
    {
        $this->assertEnvelope();
        Assert::assertIsArray($this->json['data']);
        Assert::assertIsString($this->json['data']['message'] ?? null, $this->body);
        Assert::assertStringContainsString($expectedFragment, $this->json['data']['message'], $this->body);

        return $this;
    }

    /**
     * @param array<int, string> $expectedPermissions
     */
    public function assertMissingPermissions(array $expectedPermissions): self
    {
        $this->assertEnvelope();
        Assert::assertIsArray($this->json['data']);
        Assert::assertSame('Missing permission(s)', $this->json['data']['message'] ?? null, $this->body);

        $actual = $this->json['data']['permissions'] ?? null;
        Assert::assertIsArray($actual);

        $expected = $expectedPermissions;
        sort($expected);
        sort($actual);
        Assert::assertSame($expected, $actual, $this->body);

        return $this;
    }

    public function data(): mixed
    {
        return $this->json['data'] ?? null;
    }

    public function meta(): array
    {
        $meta = $this->json['meta'] ?? [];

        return is_array($meta) ? $meta : [];
    }
}
