<?php

namespace Modules\Finance\Clients;

use App\Services\ProductOwner\RequestSigner;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Response;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Modules\Finance\ViewModels\CategoryView;
use Modules\Finance\ViewModels\TransactionView;
use RuntimeException;
use Symfony\Component\HttpFoundation\StreamedResponse;

class FinanceModuleClient
{
    private const PREFIX = '/api/v1/modules/finance';

    public function __construct(
        private readonly RequestSigner $requestSigner,
    ) {}

    /**
     * @return array<string, mixed>
     */
    public function dashboard(): array
    {
        $data = $this->get(self::PREFIX.'/dashboard');

        $data['recent_transactions'] = TransactionView::collection(
            collect($data['recent_transactions'] ?? [])->map(fn ($item) => (array) $item)->all()
        );

        $data['top_income_categories'] = collect($data['top_income_categories'] ?? [])
            ->map(fn ($item) => (object) (array) $item)
            ->all();

        $data['top_expense_categories'] = collect($data['top_expense_categories'] ?? [])
            ->map(fn ($item) => (object) (array) $item)
            ->all();

        return $data;
    }

    /**
     * @param  array<string, mixed>  $filters
     */
    public function paginateTransactions(array $filters = []): LengthAwarePaginator
    {
        return $this->paginate(
            self::PREFIX.'/transactions',
            $filters,
            fn (array $item) => TransactionView::fromArray($item)
        );
    }

    public function findTransaction(int $id): TransactionView
    {
        return TransactionView::fromArray($this->get(self::PREFIX.'/transactions/'.$id));
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function createTransaction(array $data): void
    {
        $this->post(self::PREFIX.'/transactions', $data);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function updateTransaction(int $id, array $data): void
    {
        $this->put(self::PREFIX.'/transactions/'.$id, $data);
    }

    public function deleteTransaction(int $id): void
    {
        $this->delete(self::PREFIX.'/transactions/'.$id);
    }

    /**
     * @param  array<string, mixed>  $filters
     */
    public function paginateIncomeCategories(array $filters = []): LengthAwarePaginator
    {
        return $this->paginate(
            self::PREFIX.'/income-categories',
            $filters,
            fn (array $item) => CategoryView::fromArray($item)
        );
    }

    /**
     * @param  array<string, mixed>  $filters
     */
    public function paginateExpenseCategories(array $filters = []): LengthAwarePaginator
    {
        return $this->paginate(
            self::PREFIX.'/expense-categories',
            $filters,
            fn (array $item) => CategoryView::fromArray($item)
        );
    }

    public function findIncomeCategory(int $id): CategoryView
    {
        return CategoryView::fromArray($this->get(self::PREFIX.'/income-categories/'.$id));
    }

    public function findExpenseCategory(int $id): CategoryView
    {
        return CategoryView::fromArray($this->get(self::PREFIX.'/expense-categories/'.$id));
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function createIncomeCategory(array $data): void
    {
        $this->post(self::PREFIX.'/income-categories', $data);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function createExpenseCategory(array $data): void
    {
        $this->post(self::PREFIX.'/expense-categories', $data);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function updateIncomeCategory(int $id, array $data): void
    {
        $this->put(self::PREFIX.'/income-categories/'.$id, $data);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function updateExpenseCategory(int $id, array $data): void
    {
        $this->put(self::PREFIX.'/expense-categories/'.$id, $data);
    }

    public function deleteIncomeCategory(int $id): void
    {
        $this->delete(self::PREFIX.'/income-categories/'.$id);
    }

    public function deleteExpenseCategory(int $id): void
    {
        $this->delete(self::PREFIX.'/expense-categories/'.$id);
    }

    /**
     * @param  array<string, mixed>  $filters
     * @return array<string, mixed>
     */
    public function reports(array $filters = []): array
    {
        $payload = $this->get(self::PREFIX.'/reports', $filters);

        return (array) ($payload['data'] ?? $payload);
    }

    /**
     * @param  array<string, mixed>  $filters
     */
    public function listIncomeCategories(array $filters = []): array
    {
        $response = $this->rawGet(self::PREFIX.'/income-categories', array_merge($filters, [
            'per_page' => 100,
        ]));

        return $response->json('data') ?? [];
    }

    /**
     * @param  array<string, mixed>  $filters
     */
    public function listExpenseCategories(array $filters = []): array
    {
        $response = $this->rawGet(self::PREFIX.'/expense-categories', array_merge($filters, [
            'per_page' => 100,
        ]));

        return $response->json('data') ?? [];
    }

    /**
     * @param  array<string, mixed>  $filters
     */
    public function export(string $type, array $filters = []): StreamedResponse
    {
        $response = $this->rawGet(self::PREFIX.'/exports/'.$type, $filters);

        if ($response->failed()) {
            throw new RuntimeException($response->json('message') ?? 'Export failed.');
        }

        return response()->streamDownload(function () use ($response): void {
            echo $response->body();
        }, basename($type).'.csv', [
            'Content-Type' => $response->header('Content-Type') ?? 'text/csv',
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    private function get(string $path, array $query = []): array
    {
        $response = $this->rawGet($path, $query);

        if ($response->status() === 404) {
            throw new RuntimeException($response->json('message') ?? 'Installation not found.');
        }

        $response->throw();

        $json = $response->json();

        if (is_array($json) && array_key_exists('data', $json) && is_array($json['data'])) {
            return $json['data'];
        }

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

    /**
     * @param  array<string, mixed>  $payload
     */
    private function post(string $path, array $payload = []): void
    {
        $response = $this->client()->post($path, array_merge($payload, $this->actorPayload(), [
            'installation_id' => $this->installationId(),
        ]));

        if ($response->status() === 404) {
            throw new RuntimeException($response->json('message') ?? 'Installation not found.');
        }

        $response->throw();
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function put(string $path, array $payload = []): void
    {
        $response = $this->client()->put($path, array_merge($payload, $this->actorPayload(), [
            'installation_id' => $this->installationId(),
        ]));

        if ($response->status() === 404) {
            throw new RuntimeException($response->json('message') ?? 'Installation not found.');
        }

        $response->throw();
    }

    private function delete(string $path): void
    {
        $response = $this->client()->delete($path, [
            'installation_id' => $this->installationId(),
        ]);

        if ($response->status() === 404) {
            throw new RuntimeException($response->json('message') ?? 'Installation not found.');
        }

        $response->throw();
    }

    private function rawGet(string $path, array $query = []): Response
    {
        return $this->client()->get($path, array_merge($query, [
            'installation_id' => $this->installationId(),
        ]));
    }

    /**
     * @param  array<string, mixed>  $filters
     */
    private function paginate(string $path, array $filters, callable $mapper): LengthAwarePaginator
    {
        $response = $this->rawGet($path, $filters);

        if ($response->status() === 404) {
            throw new RuntimeException($response->json('message') ?? 'Installation not found.');
        }

        $response->throw();

        $items = collect($response->json('data') ?? [])
            ->map(fn ($item) => $mapper((array) $item))
            ->all();

        return new LengthAwarePaginator(
            $items,
            (int) ($response->json('total') ?? count($items)),
            (int) ($response->json('per_page') ?? 20),
            (int) ($response->json('current_page') ?? 1),
            ['path' => request()->url(), 'query' => request()->query()]
        );
    }

    private function client(): PendingRequest
    {
        $secret = config('product_owner.api_secret');

        return Http::baseUrl(rtrim((string) config('product_owner.base_url'), '/'))
            ->timeout((int) config('product_owner.timeout', 30))
            ->acceptJson()
            ->withHeaders($secret ? [
                'X-Signature' => $this->requestSigner->signPayload(
                    ['installation_id' => $this->installationId()],
                    $secret
                ),
            ] : []);
    }

    private function installationId(): string
    {
        $installationId = config('product_owner.installation_id');

        if (empty($installationId)) {
            throw new RuntimeException('Installation is not configured.');
        }

        return (string) $installationId;
    }

    /**
     * @return array{actor_id: int|null, actor_name: string|null}
     */
    private function actorPayload(): array
    {
        $user = Auth::user();

        return [
            'actor_id' => $user?->getKey(),
            'actor_name' => $user?->name,
        ];
    }
}
