<?php

declare(strict_types=1);

namespace ApiModel\Tests\Unit\Budget;

use ApiModel\Domain\Repository\BudgetRepositoryInterface;
use ApiModel\Service\BudgetService;
use ApiModel\Shared\Exceptions\BadRequestException;
use ApiModel\Shared\Exceptions\NotFoundException;
use PHPUnit\Framework\TestCase;
use PDO;

final class BudgetServiceTest extends TestCase
{
    public function testVerifyAvailabilityBypassesForIncomeClassifiers(): void
    {
        $repoMock = $this->createMock(BudgetRepositoryInterface::class);
        $pdoMock = $this->createMock(PDO::class);

        // Mock findClassifierById to return an income type
        $repoMock->method('findClassifierById')
            ->with('income-id')
            ->willReturn([
                'id' => 'income-id',
                'code' => '1.1',
                'name' => 'Taxes',
                'type' => 'income'
            ]);

        // If it bypasses, it won't query getAvailability
        $repoMock->expects($this->never())->method('getAvailability');

        $service = new BudgetService($repoMock, $pdoMock);
        $service->verifyAvailability('fy-id', 'org-id', 'income-id', 5000.00);

        $this->addToAssertionCount(1);
    }

    public function testVerifyAvailabilityAllowsIfAvailableAmountIsSufficient(): void
    {
        $repoMock = $this->createMock(BudgetRepositoryInterface::class);
        $pdoMock = $this->createMock(PDO::class);

        // Expense classifier
        $repoMock->method('findClassifierById')
            ->with('expense-id')
            ->willReturn([
                'id' => 'expense-id',
                'code' => '2.1',
                'name' => 'Personnel',
                'type' => 'expense'
            ]);

        // Available: 100.00
        $repoMock->method('getAvailability')
            ->with('fy-id', 'org-id', 'expense-id')
            ->willReturn([
                'approved_amount' => 100.0,
                'current_amount' => 100.0,
                'precommitted' => 0.0,
                'committed' => 0.0,
                'accrued' => 0.0,
                'paid' => 0.0,
                'available' => 100.0
            ]);

        $service = new BudgetService($repoMock, $pdoMock);
        
        // Requesting 45.00 (within budget)
        $service->verifyAvailability('fy-id', 'org-id', 'expense-id', 45.00);

        $this->addToAssertionCount(1);
    }

    public function testVerifyAvailabilityThrowsBadRequestIfAmountExceedsAvailable(): void
    {
        $repoMock = $this->createMock(BudgetRepositoryInterface::class);
        $pdoMock = $this->createMock(PDO::class);

        // Expense classifier
        $repoMock->method('findClassifierById')
            ->with('expense-id')
            ->willReturn([
                'id' => 'expense-id',
                'code' => '2.1',
                'name' => 'Personnel',
                'type' => 'expense'
            ]);

        // Available: 100.00
        $repoMock->method('getAvailability')
            ->with('fy-id', 'org-id', 'expense-id')
            ->willReturn([
                'approved_amount' => 100.0,
                'current_amount' => 100.0,
                'precommitted' => 0.0,
                'committed' => 0.0,
                'accrued' => 0.0,
                'paid' => 0.0,
                'available' => 100.0
            ]);

        $service = new BudgetService($repoMock, $pdoMock);

        // Expect exception since 150.00 > 100.00
        $this->expectException(BadRequestException::class);
        $this->expectExceptionMessage('Saldo presupuestario insuficiente');

        $service->verifyAvailability('fy-id', 'org-id', 'expense-id', 150.00);
    }

    public function testVerifyAvailabilityThrowsNotFoundIfClassifierDoesNotExist(): void
    {
        $repoMock = $this->createMock(BudgetRepositoryInterface::class);
        $pdoMock = $this->createMock(PDO::class);

        $repoMock->method('findClassifierById')
            ->with('fake-id')
            ->willReturn(null);

        $service = new BudgetService($repoMock, $pdoMock);

        $this->expectException(NotFoundException::class);
        $service->verifyAvailability('fy-id', 'org-id', 'fake-id', 10.00);
    }
}
