<?php

namespace MintyPHP\Tests\Module\Helpdesk\Service;

use MintyPHP\Http\SessionStoreInterface;
use MintyPHP\Module\Helpdesk\Service\BcODataGateway;
use MintyPHP\Module\Helpdesk\Service\HelpdeskOAuthTokenService;
use MintyPHP\Module\Helpdesk\Service\EffectiveHelpdeskSettingsService;
use PHPUnit\Framework\TestCase;

class BcODataGatewayTest extends TestCase
{
    private function createGatewayWithSettings(bool $isConfigured = true, ?SessionStoreInterface $sessionStore = null): BcODataGateway
    {
        $settings = $this->createMock(EffectiveHelpdeskSettingsService::class);
        $settings->method('isConfigured')->willReturn($isConfigured);
        $settings->method('getODataBaseUrl')->willReturn('https://bc.example.com/ODataV4');
        $settings->method('getCompanyName')->willReturn('Test Company');
        $settings->method('buildEntityUrl')->willReturnCallback(
            static fn (string $entity): string => "https://bc.example.com/ODataV4/Company('Test%20Company')/" . $entity
        );
        $settings->method('getAuthMode')->willReturn('basic');
        $settings->method('getBasicUser')->willReturn('testuser');
        $settings->method('getBasicPassword')->willReturn('testpass');

        $oauthTokenService = $this->createMock(HelpdeskOAuthTokenService::class);
        if ($sessionStore === null) {
            $sessionStore = $this->createMock(SessionStoreInterface::class);
            $sessionStore->method('all')->willReturn([
                'current_tenant' => ['id' => 1],
            ]);
        }

        return new BcODataGateway($settings, $oauthTokenService, $sessionStore);
    }

    public function testSearchCustomersReturnsEmptyForEmptyQuery(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->searchCustomers('');
        $this->assertSame([], $results);
    }

    public function testSearchCustomersReturnsEmptyForWhitespaceQuery(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->searchCustomers('   ');
        $this->assertSame([], $results);
    }

    public function testGetCustomerReturnsNullForEmptyNo(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $result = $gateway->getCustomer('');
        $this->assertNull($result);
    }

    public function testGetContactsForCustomerReturnsEmptyForEmptyName(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->getContactsForCustomer('10254', '');
        $this->assertSame([], $results);
    }

    public function testGetTicketsForCustomerReturnsEmptyForEmptyName(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->getTicketsForCustomer('10254', '');
        $this->assertSame([], $results);
    }

    public function testGetTicketReturnsNullForEmptyNo(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $result = $gateway->getTicket('');
        $this->assertNull($result);
    }

    public function testGetContractsForCustomerReturnsEmptyForEmptyNo(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->getContractsForCustomer('');
        $this->assertSame([], $results);
    }

    public function testGetEscalationTicketsForCustomerReturnsEmptyForEmptyNo(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->getEscalationTicketsForCustomer('');
        $this->assertSame([], $results);
    }

    public function testSearchCustomersThrowsWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $this->expectException(\RuntimeException::class);
        $gateway->searchCustomers('test');
    }

    public function testGetCustomerReturnsNullWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $result = $gateway->getCustomer('10001');
        $this->assertNull($result);
    }

    public function testGetTicketReturnsNullWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $result = $gateway->getTicket('T0001');
        $this->assertNull($result);
    }

    public function testGetContractsForCustomerReturnsEmptyWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $results = $gateway->getContractsForCustomer('10610');
        $this->assertSame([], $results);
    }

    public function testGetEscalationDefinitionsReturnsEmptyWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $results = $gateway->getEscalationDefinitions();
        $this->assertSame([], $results);
    }

    public function testGetEscalationDefinitionsReturnsCachedValuesWhenAvailable(): void
    {
        $sessionStore = $this->createMock(SessionStoreInterface::class);
        $sessionStore->method('all')->willReturn([
            'current_tenant' => ['id' => 7],
        ]);
        $sessionStore->expects($this->any())->method('get')->with('module.helpdesk.debitor.v1.escalation-definitions.7')->willReturn([
            'values' => [
                ['Code' => 'MAX', 'Target_Time' => 'P1DT0H0M0.0S'],
            ],
            'fetched_at' => time(),
        ]);
        $sessionStore->expects($this->never())->method('set');

        $gateway = $this->createGatewayWithSettings(true, $sessionStore);
        $results = $gateway->getEscalationDefinitions();

        $this->assertCount(1, $results);
        $this->assertSame('MAX', $results[0]['Code']);
    }

    public function testGetEscalationTicketsForCustomerReturnsEmptyWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $results = $gateway->getEscalationTicketsForCustomer('10610');
        $this->assertSame([], $results);
    }

    public function testTestConnectionReturnsNotOkWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $result = $gateway->testConnection();
        $this->assertFalse($result['ok']);
        $this->assertSame('Configuration incomplete', $result['error']);
    }

    public function testSearchCustomersRejectsODataInjectionWithParentheses(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $this->expectException(\InvalidArgumentException::class);
        $gateway->searchCustomers("') or true or contains(Name,'");
    }

    public function testSearchCustomersRejectsODataInjectionWithSingleQuotes(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $this->expectException(\InvalidArgumentException::class);
        $gateway->searchCustomers("test' eq 'hack");
    }

    public function testGetCustomerRejectsODataInjectionCharacters(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $this->expectException(\InvalidArgumentException::class);
        $gateway->getCustomer("10254' or '1'='1");
    }

    public function testSearchCustomersAllowsGermanUmlauts(): void
    {
        // Should not throw InvalidArgumentException — umlauts are valid search characters
        // Will throw RuntimeException because cURL can't reach fake URL, which is expected
        $gateway = $this->createGatewayWithSettings();
        try {
            $gateway->searchCustomers('Müller Straße');
        } catch (\InvalidArgumentException) {
            $this->fail('Umlauts should be allowed in search queries');
        } catch (\RuntimeException) {
            // Expected: cURL fails against fake URL
            $this->addToAssertionCount(1);
        }
    }

    public function testSearchCustomersAllowsNumbersAndDashes(): void
    {
        $gateway = $this->createGatewayWithSettings();
        try {
            $gateway->searchCustomers('10254-A');
        } catch (\InvalidArgumentException) {
            $this->fail('Numbers and dashes should be allowed in search queries');
        } catch (\RuntimeException) {
            // Expected: cURL fails against fake URL
            $this->addToAssertionCount(1);
        }
    }

    public function testGetTicketLogReturnsEmptyForEmptyNo(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->getTicketLog('');
        $this->assertSame([], $results);
    }

    public function testGetTicketLogReturnsEmptyWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $results = $gateway->getTicketLog('T26-1214');
        $this->assertSame([], $results);
    }

    public function testGetContactsForCustomerReturnsEmptyWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $results = $gateway->getContactsForCustomer('10254', 'Test GmbH');
        $this->assertSame([], $results);
    }

    public function testGetTicketsForCustomerReturnsEmptyWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $results = $gateway->getTicketsForCustomer('10254', 'Test GmbH');
        $this->assertSame([], $results);
    }

    public function testGetContactsForCustomerAllowsSpecialCharactersInCompanyName(): void
    {
        $gateway = $this->createGatewayWithSettings();
        try {
            $gateway->getContactsForCustomer('10636', 'Behindertenwerkstätten Oberpfalz – Betreuungs GmbH');
            $this->addToAssertionCount(1);
        } catch (\InvalidArgumentException) {
            $this->fail('Trusted customer literals should not be rejected for contact loads.');
        } catch (\RuntimeException) {
            // Expected: cURL fails against fake URL.
            $this->addToAssertionCount(1);
        }
    }

    public function testGetTicketsForCustomerAllowsSpecialCharactersInCompanyName(): void
    {
        $gateway = $this->createGatewayWithSettings();
        try {
            $gateway->getTicketsForCustomer('10494', "J.G. Knopf´s Sohn Gmbh & Co. KG");
            $this->addToAssertionCount(1);
        } catch (\InvalidArgumentException) {
            $this->fail('Trusted customer literals should not be rejected for ticket loads.');
        } catch (\RuntimeException) {
            // Expected: cURL fails against fake URL.
            $this->addToAssertionCount(1);
        }
    }

    public function testListCustomersThrowsWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $this->expectException(\RuntimeException::class);
        $gateway->listCustomers('', '', 10, 0, 'Name', 'asc');
    }

    public function testListCustomersRejectsInvalidCityFilterCharacters(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $this->expectException(\InvalidArgumentException::class);
        $gateway->listCustomers('', 'Berlin)', 10, 0, 'Name', 'asc');
    }

    public function testGetContractLinesForCustomerReturnsEmptyForEmptyNo(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->getContractLinesForCustomer('');
        $this->assertSame([], $results);
    }

    public function testGetContractLinesForCustomerReturnsEmptyForWhitespace(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->getContractLinesForCustomer('   ');
        $this->assertSame([], $results);
    }

    public function testGetTicketsForControllingReturnsEmptyForBlankCustomerNo(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->getTicketsForControlling('');
        $this->assertSame([], $results);
    }

    public function testGetTicketsForControllingReturnsEmptyForWhitespace(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->getTicketsForControlling('   ');
        $this->assertSame([], $results);
    }

    public function testGetTicketsForControllingReturnsEmptyWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $results = $gateway->getTicketsForControlling('10610');
        $this->assertSame([], $results);
    }

    // ── Meetings: empty / whitespace / not-configured ──────────────

    public function testGetMeetingsForCustomerReturnsEmptyForEmptyNo(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->getMeetingsForCustomer('');
        $this->assertSame([], $results);
    }

    public function testGetMeetingsForCustomerReturnsEmptyForWhitespace(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $results = $gateway->getMeetingsForCustomer('   ');
        $this->assertSame([], $results);
    }

    public function testGetMeetingsForCustomerReturnsEmptyWhenNotConfigured(): void
    {
        $gateway = $this->createGatewayWithSettings(false);
        $results = $gateway->getMeetingsForCustomer('10610');
        $this->assertSame([], $results);
    }

    // ── OData injection rejection on customerNo methods ────────────

    public function testGetMeetingsForCustomerRejectsODataInjection(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $this->expectException(\InvalidArgumentException::class);
        $gateway->getMeetingsForCustomer("10254' or '1'='1");
    }

    public function testGetContractLinesForCustomerRejectsODataInjection(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $this->expectException(\InvalidArgumentException::class);
        $gateway->getContractLinesForCustomer("10254' or '1'='1");
    }

    public function testGetEscalationTicketsForCustomerRejectsODataInjection(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $this->expectException(\InvalidArgumentException::class);
        $gateway->getEscalationTicketsForCustomer("10254' or '1'='1");
    }

    public function testGetTicketsForControllingRejectsODataInjection(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $this->expectException(\InvalidArgumentException::class);
        $gateway->getTicketsForControlling("10254' or '1'='1");
    }

    public function testGetMeetingsForCustomerRejectsParenthesesInjection(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $this->expectException(\InvalidArgumentException::class);
        $gateway->getMeetingsForCustomer("') or true or contains(Name,'");
    }

    public function testGetContractLinesForCustomerRejectsParenthesesInjection(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $this->expectException(\InvalidArgumentException::class);
        $gateway->getContractLinesForCustomer("10254) or (1 eq 1");
    }

    // ── Valid customerNo values still pass strict validation ───────

    public function testGetMeetingsForCustomerAcceptsValidCustomerNo(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $threw = false;
        try {
            $gateway->getMeetingsForCustomer('10254');
        } catch (\InvalidArgumentException) {
            $this->fail('Valid customer number should pass strict input validation');
        } catch (\RuntimeException) {
            $threw = true;
        }
        // Either cURL failed (expected) or returned empty — both are fine
        $this->addToAssertionCount(1);
    }

    public function testGetContractLinesForCustomerAcceptsCustomerNoWithDash(): void
    {
        $gateway = $this->createGatewayWithSettings();
        $threw = false;
        try {
            $gateway->getContractLinesForCustomer('10254-A');
        } catch (\InvalidArgumentException) {
            $this->fail('Customer number with dash should pass strict input validation');
        } catch (\RuntimeException) {
            $threw = true;
        }
        $this->addToAssertionCount(1);
    }
}
