<?php
namespace Tests\Unit\api\Controllers;

use PHPUnit\Framework\TestCase;
use api\Controllers\OrdersApiController;
use Domain\Order\OrderAdminService;
use Domain\Order\OrderRepository;

class OrdersApiControllerTest extends TestCase
{
    private $mockService;
    private $mockOrderRepo;
    private $controller;

    protected function setUp(): void
    {
        $this->mockService = $this->createMock(OrderAdminService::class);
        $this->mockOrderRepo = $this->createMock(OrderRepository::class);
        $this->controller = new OrdersApiController($this->mockService, $this->mockOrderRepo);

        $_SERVER['REQUEST_METHOD'] = 'GET';
        $_GET = [];
    }

    protected function tearDown(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'GET';
        $_GET = [];
        http_response_code(200);
    }

    // --- list ---

    public function testListReturnsOrders(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'GET';

        $this->mockOrderRepo->method('listForApi')
            ->willReturn([
                'items' => [
                    ['id' => 1, 'number' => '2026/01/001', 'status' => 0, 'paid' => 0, 'summary' => 99.99],
                ],
                'total' => 1,
                'page' => 1,
                'per_page' => 50,
            ]);

        ob_start();
        $this->controller->list();
        $output = ob_get_clean();

        $json = json_decode($output, true);
        $this->assertSame('ok', $json['status']);
        $this->assertCount(1, $json['data']['items']);
        $this->assertSame(1, $json['data']['total']);
    }

    public function testListRejectsPostMethod(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'POST';

        ob_start();
        $this->controller->list();
        $output = ob_get_clean();

        $this->assertSame(405, http_response_code());
    }

    public function testListPassesFiltersToRepository(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'GET';
        $_GET['status'] = '4';
        $_GET['paid'] = '1';
        $_GET['page'] = '2';
        $_GET['per_page'] = '25';

        $this->mockOrderRepo->expects($this->once())
            ->method('listForApi')
            ->with(
                $this->callback(function ($filters) {
                    return $filters['status'] === '4' && $filters['paid'] === '1';
                }),
                2,
                25
            )
            ->willReturn(['items' => [], 'total' => 0, 'page' => 2, 'per_page' => 25]);

        ob_start();
        $this->controller->list();
        ob_get_clean();
    }

    // --- get ---

    public function testGetReturnsOrder(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'GET';
        $_GET['id'] = '42';

        $this->mockOrderRepo->method('findForApi')
            ->with(42)
            ->willReturn([
                'id' => 42,
                'number' => '2026/01/001',
                'status' => 4,
                'paid' => 1,
                'summary' => 150.00,
                'products' => [],
                'statuses' => [],
            ]);

        ob_start();
        $this->controller->get();
        $output = ob_get_clean();

        $json = json_decode($output, true);
        $this->assertSame('ok', $json['status']);
        $this->assertSame(42, $json['data']['id']);
    }

    public function testGetReturns404WhenOrderNotFound(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'GET';
        $_GET['id'] = '999';

        $this->mockOrderRepo->method('findForApi')
            ->with(999)
            ->willReturn(null);

        ob_start();
        $this->controller->get();
        $output = ob_get_clean();

        $this->assertSame(404, http_response_code());
        $json = json_decode($output, true);
        $this->assertSame('NOT_FOUND', $json['code']);
    }

    public function testGetReturns400WhenMissingId(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'GET';

        ob_start();
        $this->controller->get();
        $output = ob_get_clean();

        $this->assertSame(400, http_response_code());
    }

    // --- change_status ---

    public function testChangeStatusUpdatesOrder(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'PUT';
        $_GET['id'] = '10';

        $this->mockOrderRepo->method('findRawById')
            ->with(10)
            ->willReturn(['id' => 10, 'status' => 0]);

        $this->mockService->method('changeStatus')
            ->with(10, 5, false)
            ->willReturn(['result' => true]);

        // Simulate JSON body via php://input override is not possible in unit tests,
        // so we test the controller logic path via mock expectations
        ob_start();
        $this->controller->change_status();
        $output = ob_get_clean();

        // Without a real php://input body, getJsonBody returns null → BAD_REQUEST
        $json = json_decode($output, true);
        $this->assertSame('error', $json['status']);
        $this->assertSame('BAD_REQUEST', $json['code']);
    }

    public function testChangeStatusReturns400WhenMissingId(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'PUT';

        ob_start();
        $this->controller->change_status();
        $output = ob_get_clean();

        $this->assertSame(400, http_response_code());
    }

    public function testChangeStatusRejectsGetMethod(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'GET';
        $_GET['id'] = '10';

        ob_start();
        $this->controller->change_status();
        $output = ob_get_clean();

        $this->assertSame(405, http_response_code());
    }

    // --- set_paid ---

    public function testSetPaidReturns404WhenOrderNotFound(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'PUT';
        $_GET['id'] = '999';

        $this->mockOrderRepo->method('findRawById')
            ->with(999)
            ->willReturn(null);

        ob_start();
        $this->controller->set_paid();
        $output = ob_get_clean();

        $this->assertSame(404, http_response_code());
    }

    public function testSetPaidReturns400WhenMissingId(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'PUT';

        ob_start();
        $this->controller->set_paid();
        $output = ob_get_clean();

        $this->assertSame(400, http_response_code());
    }

    public function testSetPaidCallsServiceWhenOrderExists(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'PUT';
        $_GET['id'] = '10';

        $this->mockOrderRepo->method('findRawById')
            ->with(10)
            ->willReturn(['id' => 10, 'paid' => 0]);

        $this->mockService->expects($this->once())
            ->method('setOrderAsPaid')
            ->with(10, false);

        ob_start();
        $this->controller->set_paid();
        $output = ob_get_clean();

        $json = json_decode($output, true);
        $this->assertSame('ok', $json['status']);
        $this->assertSame(1, $json['data']['paid']);
    }

    // --- set_unpaid ---

    public function testSetUnpaidReturns404WhenOrderNotFound(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'PUT';
        $_GET['id'] = '999';

        $this->mockOrderRepo->method('findRawById')
            ->with(999)
            ->willReturn(null);

        ob_start();
        $this->controller->set_unpaid();
        $output = ob_get_clean();

        $this->assertSame(404, http_response_code());
    }

    public function testSetUnpaidCallsServiceWhenOrderExists(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'PUT';
        $_GET['id'] = '10';

        $this->mockOrderRepo->method('findRawById')
            ->with(10)
            ->willReturn(['id' => 10, 'paid' => 1]);

        $this->mockService->expects($this->once())
            ->method('setOrderAsUnpaid')
            ->with(10);

        ob_start();
        $this->controller->set_unpaid();
        $output = ob_get_clean();

        $json = json_decode($output, true);
        $this->assertSame('ok', $json['status']);
        $this->assertSame(0, $json['data']['paid']);
    }
}
