<?php

app_require('classes/economic_v2_versioning_service.php');
app_require('classes/economic_v2_distribution_service.php');

use classes\economic_v2_distribution_service;
use classes\economic_v2_versioning_service;

if (!class_exists('FakeEconomicV2BookedDepartment75VersioningService')) {
    class FakeEconomicV2BookedDepartment75VersioningService extends economic_v2_versioning_service
    {
        public ?array $fixedVersion = null;
        public array $subscriptionVersions = [];

        public function __construct()
        {
        }

        public function resolveFixedPricingVersionAt(int $customer_number, string $timestamp): ?array
        {
            return $this->fixedVersion;
        }

        public function resolveVehicleSubscriptionVersionsAt(int $customer_number, string $timestamp): array
        {
            return $this->subscriptionVersions;
        }

        public function resolveDiscountOverrideAt(int $customer_number, bool $is_category, string|int $object_id, string $timestamp, ?int $department_id = null): ?array
        {
            return null;
        }

        public function runBestEffortBackfill(): array
        {
            return [
                'fixed_pricing' => ['inserted' => 0, 'updated' => 0, 'closed' => 0, 'noop' => 0],
                'vehicle_subscriptions' => ['inserted' => 0, 'updated' => 0, 'closed' => 0, 'noop' => 0],
                'discount_overrides' => ['inserted' => 0, 'updated' => 0, 'closed' => 0, 'noop' => 0],
                'inferred' => ['fixed_pricing' => 0, 'vehicle_subscriptions' => 0],
                'warnings' => [],
            ];
        }
    }
}

if (!class_exists('TestableEconomicV2BookedDepartment75DistributionService')) {
    class TestableEconomicV2BookedDepartment75DistributionService extends economic_v2_distribution_service
    {
        public array $stubOrders = [];
        public array $stubOrderItems = [];
        public array $stubVersionRows = [];
        public array $subscriptionPrices = [];
        public array $stubBookedInvoices = [];
        public array $stubBookedInvoiceLines = [];
        public int $fallbackDepartmentId = 8;
        public array $customerDefaultDepartments = [];
        public ?array $includedCustomers = null;

        public function __construct(?economic_v2_versioning_service $versioning = null)
        {
            parent::__construct($versioning);
        }

        protected function fetchOrdersInRange(string $from_ts, string $to_ts): array
        {
            return $this->stubOrders;
        }

        protected function fetchOrderItemsByOrderIds(array $order_ids): array
        {
            return $this->stubOrderItems;
        }

        protected function fetchVehicleSubscriptionVersionRows(string $from_ts, string $to_ts): array
        {
            return $this->stubVersionRows;
        }

        protected function calculateOrderOriginalPrice(array $order_items, int $customer_number, int $department_id, string $timestamp): float
        {
            $total = 0.0;
            foreach ($order_items as $item) {
                $total += (float)($item['price'] ?? 0.0) * (float)($item['quantity'] ?? 0.0);
            }

            return $total;
        }

        protected function getSubscriptionMonthlyPrice(int $vehicle_type): float
        {
            return (float)($this->subscriptionPrices[$vehicle_type] ?? 0.0);
        }

        protected function fetchBookedInvoicesInDateRange(string $date_from, string $date_to, array &$warnings): array
        {
            return $this->stubBookedInvoices;
        }

        protected function fetchBookedInvoiceLines(array $invoice_ids, array &$warnings): array
        {
            return $this->stubBookedInvoiceLines;
        }

        protected function buildCustomerEnvelope(int $customer_number, array $transaction_map): array
        {
            return [
                'id' => $customer_number,
                'customer_number' => $customer_number,
                'customer_name' => 'Customer ' . $customer_number,
                'transactions' => array_values($transaction_map),
                'requires_action' => false,
                'meta' => [],
            ];
        }

        protected function buildTransactionObject(int $order_id, string $created_at, int $department_id, ?float $amount = null, ?bool $included = null): array
        {
            return [
                'id' => $order_id,
                'date' => $created_at,
                'amount' => round((float)($amount ?? 0.0), 5),
                'booked' => true,
                'department_id' => $department_id,
                'excluded' => !($included ?? $this->isDepartmentEligible($department_id)),
            ];
        }

        protected function isDepartmentEligible(int $department_id): bool
        {
            return $department_id > 0 && $department_id !== 10;
        }

        protected function shouldIncludeCustomerNumber(int $customer_number): bool
        {
            if ($this->includedCustomers === null) {
                return true;
            }

            return in_array($customer_number, $this->includedCustomers, true);
        }

        protected function getCustomerDefaultDepartmentId(int $customer_number): ?int
        {
            return $this->customerDefaultDepartments[$customer_number] ?? null;
        }

        protected function getFallbackDistributionDepartmentId(): int
        {
            return $this->fallbackDepartmentId;
        }

        protected function parseDepartmentMap(array $department_map): array
        {
            $parsed = [];
            foreach ($department_map as $department_id => $amount) {
                $parsed['Department ' . $department_id] = round((float)$amount, 5);
            }

            return $parsed;
        }

        protected function versionTableHasRows(string $table): bool
        {
            return true;
        }
    }
}

it('redistributes booked department 75 net amounts using fixed pricing and wash subscription weights', function (): void {
    $versioning = new FakeEconomicV2BookedDepartment75VersioningService();
    $versioning->fixedVersion = [
        'id' => 91,
        'price' => 500.0,
        'description' => 'Fixed pricing agreement',
        'source' => 'test.fixed',
        'confidence' => 1.0,
        'inferred' => false,
        'effective_from' => '2026-01-01 00:00:00',
        'effective_to' => null,
    ];
    $versioning->subscriptionVersions = [[
        'id' => 42,
        'reg' => 'AB12345',
        'vehicle_type' => 77,
        'source' => 'test.subscription',
        'confidence' => 1.0,
        'inferred' => false,
    ]];

    $service = new TestableEconomicV2BookedDepartment75DistributionService($versioning);
    $service->subscriptionPrices = [
        77 => 120.0,
    ];
    $service->stubOrders = [
        [
            'id' => 101,
            'customer_id' => 12345,
            'department_id' => 1,
            'created_at' => '2026-01-10 10:00:00',
            'reg_1' => '',
            'reference' => 'Internal fixed pricing basis',
        ],
        [
            'id' => 102,
            'customer_id' => 12345,
            'department_id' => 2,
            'created_at' => '2026-01-11 10:00:00',
            'reg_1' => 'AB12345',
            'reference' => '',
        ],
    ];
    $service->stubOrderItems = [
        101 => [[
            'product_id' => 61,
            'price' => 400.0,
            'quantity' => 1,
            'reference' => '',
        ]],
        102 => [[
            'product_id' => 77,
            'price' => 60.0,
            'quantity' => 2,
            'reference' => 'AB12345',
        ]],
    ];
    $service->stubBookedInvoices = [[
        'bookedInvoiceNumber' => 7001,
        'date' => '2026-01-31',
        'customer' => [
            'customerNumber' => 12345,
        ],
    ]];
    $service->stubBookedInvoiceLines = [
        7001 => [
            ['lineNumber' => 1, 'description' => '[ 01/01/2026 00:00 Auto #999 ]'],
            ['lineNumber' => 2, 'description' => 'Reference:'],
            ['lineNumber' => 3, 'description' => '# Fast pris aftale'],
            [
                'lineNumber' => 4,
                'description' => 'Fixed price',
                'quantity' => 1,
                'unitNetPrice' => 200,
                'totalNetAmount' => 200,
                'product' => ['productNumber' => 61],
                'departmentalDistribution' => [
                    'distributions' => [
                        ['percentage' => 50, 'department' => ['departmentNumber' => 75]],
                        ['percentage' => 50, 'department' => ['departmentNumber' => 1]],
                    ],
                ],
            ],
            [
                'lineNumber' => 5,
                'description' => 'Rabat',
                'quantity' => 1,
                'unitNetPrice' => -40,
                'totalNetAmount' => -40,
                'product' => ['productNumber' => 'TotDiscount'],
                'departmentalDistribution' => [
                    'distributions' => [
                        ['percentage' => 50, 'department' => ['departmentNumber' => 75]],
                        ['percentage' => 50, 'department' => ['departmentNumber' => 1]],
                    ],
                ],
            ],
            ['lineNumber' => 6, 'description' => '[ 01/01/2026 00:00 Auto #1000 ]'],
            ['lineNumber' => 7, 'description' => 'Reference:'],
            ['lineNumber' => 8, 'description' => '# Vaskeabonnementer'],
            [
                'lineNumber' => 9,
                'description' => 'Subscription',
                'quantity' => 1,
                'unitNetPrice' => 120,
                'totalNetAmount' => 120,
                'product' => ['productNumber' => 77],
                'departmentalDistribution' => [
                    'distributions' => [
                        ['percentage' => 100, 'department' => ['departmentNumber' => 75]],
                    ],
                ],
            ],
        ],
    ];

    $result = $service->getBookedDepartment75Distribution('2026-01-01', '2026-01-31');

    expect($result['warnings'])->toBe([]);
    expect($result['customers'])->toHaveCount(1);

    $meta = $result['customers'][0]['meta']['booked_department_75'];
    expect($meta['booked_net_amount'])->toBe(200.0);
    expect($meta['distributed_net_amount'])->toBe(200.0);
    expect($meta['undistributed_net_amount'])->toBe(0.0);
    expect($meta['department_distribution']['1'])->toBe(61.53846);
    expect($meta['department_distribution']['2'])->toBe(138.46154);

    $groups = [];
    foreach ($meta['booked_groups'] as $group) {
        $groups[$group['source_category']] = $group;
    }

    expect($groups['fixed_pricing']['booked_net_amount'])->toBe(80.0);
    expect($groups['fixed_pricing']['department_distribution']['1'])->toBe(61.53846);
    expect($groups['fixed_pricing']['department_distribution']['2'])->toBe(18.46154);
    expect($groups['fixed_pricing']['undistributed_net_amount'])->toBe(0.0);
    expect($groups['wash_subscriptions']['booked_net_amount'])->toBe(120.0);
    expect($groups['wash_subscriptions']['department_distribution']['2'])->toBe(120.0);
    expect($groups['wash_subscriptions']['undistributed_net_amount'])->toBe(0.0);

    expect($result['collective_results']['booked_net_amount'])->toBe(200.0);
    expect($result['collective_results']['distributed_net_amount'])->toBe(200.0);
    expect($result['collective_results']['undistributed_net_amount'])->toBe(0.0);
    expect($result['collective_results']['department_distribution']['1'])->toBe(61.53846);
    expect($result['collective_results']['department_distribution']['2'])->toBe(138.46154);
});

it('assigns classified booked department 75 amounts to fallback when no monthly basis exists', function (): void {
    $service = new TestableEconomicV2BookedDepartment75DistributionService(new FakeEconomicV2BookedDepartment75VersioningService());
    $service->stubBookedInvoices = [[
        'bookedInvoiceNumber' => 7002,
        'date' => '2026-01-31',
        'customer' => [
            'customerNumber' => 67890,
        ],
    ]];
    $service->stubBookedInvoiceLines = [
        7002 => [
            ['lineNumber' => 1, 'description' => '[ 01/01/2026 00:00 Auto #1001 ]'],
            ['lineNumber' => 2, 'description' => 'Reference:'],
            ['lineNumber' => 3, 'description' => '# Fast pris aftale'],
            [
                'lineNumber' => 4,
                'description' => 'Fixed price',
                'quantity' => 1,
                'unitNetPrice' => 60,
                'totalNetAmount' => 60,
                'product' => ['productNumber' => 61],
                'departmentalDistribution' => [
                    'distributions' => [
                        ['percentage' => 100, 'department' => ['departmentNumber' => 75]],
                    ],
                ],
            ],
        ],
    ];

    $result = $service->getBookedDepartment75Distribution('2026-01-01', '2026-01-31');

    expect($result['customers'])->toHaveCount(1);
    $meta = $result['customers'][0]['meta']['booked_department_75'];
    expect($meta['booked_groups'][0]['source_category'])->toBe('fixed_pricing');
    expect($meta['distributed_net_amount'])->toBe(60.0);
    expect($meta['undistributed_net_amount'])->toBe(0.0);
    expect($meta['department_distribution']['8'])->toBe(60.0);
    expect($meta['booked_groups'][0]['department_distribution']['8'])->toBe(60.0);
    expect($meta['booked_groups'][0]['undistributed_net_amount'])->toBe(0.0);
    expect($result['collective_results']['distributed_net_amount'])->toBe(60.0);
    expect($result['collective_results']['undistributed_net_amount'])->toBe(0.0);
    expect($result['collective_results']['department_distribution']['8'])->toBe(60.0);
    expect(implode("\n", $result['warnings']))->toContain('has no redistribution basis');
    expect(implode("\n", $result['warnings']))->toContain('assigned to fallback department 8');
});

it('keeps unclassified booked department 75 lines undistributed with warnings', function (): void {
    $service = new TestableEconomicV2BookedDepartment75DistributionService(new FakeEconomicV2BookedDepartment75VersioningService());
    $service->stubBookedInvoices = [[
        'bookedInvoiceNumber' => 7003,
        'date' => '2026-01-31',
        'customer' => [
            'customerNumber' => 77777,
        ],
    ]];
    $service->stubBookedInvoiceLines = [
        7003 => [
            ['lineNumber' => 1, 'description' => '[ 01/01/2026 00:00 Auto #1002 ]'],
            [
                'lineNumber' => 2,
                'description' => 'Unknown booked line',
                'quantity' => 1,
                'unitNetPrice' => 25,
                'totalNetAmount' => 25,
                'product' => ['productNumber' => 99],
                'departmentalDistribution' => [
                    'distributions' => [
                        ['percentage' => 100, 'department' => ['departmentNumber' => 75]],
                    ],
                ],
            ],
        ],
    ];

    $result = $service->getBookedDepartment75Distribution('2026-01-01', '2026-01-31');

    expect($result['customers'])->toHaveCount(1);
    expect($result['customers'][0]['meta']['booked_department_75']['booked_groups'][0]['source_category'])->toBe('unclassified');
    expect($result['customers'][0]['meta']['booked_department_75']['distributed_net_amount'])->toBe(0.0);
    expect($result['customers'][0]['meta']['booked_department_75']['undistributed_net_amount'])->toBe(25.0);
    expect($result['collective_results']['undistributed_net_amount'])->toBe(25.0);
    $warning_text = implode("\n", $result['warnings']);
    expect($warning_text)->toContain('Unable to classify booked department 75 line');
    expect($warning_text)->toContain('could not be classified and remains undistributed');
});

it('supports bulk booked invoice line payloads with top-level department numbers', function (): void {
    $versioning = new FakeEconomicV2BookedDepartment75VersioningService();
    $versioning->fixedVersion = [
        'id' => 92,
        'price' => 100.0,
        'description' => 'Fixed pricing agreement',
        'source' => 'test.fixed.bulk',
        'confidence' => 1.0,
        'inferred' => false,
        'effective_from' => '2026-01-01 00:00:00',
        'effective_to' => null,
    ];

    $service = new TestableEconomicV2BookedDepartment75DistributionService($versioning);
    $service->stubOrders = [[
        'id' => 201,
        'customer_id' => 54321,
        'department_id' => 3,
        'created_at' => '2026-01-10 09:00:00',
        'reg_1' => '',
        'reference' => 'Internal fixed pricing basis',
    ]];
    $service->stubOrderItems = [
        201 => [[
            'product_id' => 41,
            'price' => 100.0,
            'quantity' => 1,
            'reference' => '',
        ]],
    ];
    $service->stubBookedInvoices = [[
        'bookedInvoiceNumber' => 7004,
        'date' => '2026-01-31',
        'customer' => [
            'customerNumber' => 54321,
        ],
    ]];
    $service->stubBookedInvoiceLines = [
        7004 => [
            ['number' => 1, 'description' => '[ 01/01/2026 00:00 Auto #1002 ]'],
            ['number' => 2, 'description' => '# Fast pris aftale'],
            [
                'number' => 3,
                'description' => 'Fixed price',
                'productNumber' => '41',
                'quantity' => 1,
                'unitNetPrice' => 100,
                'totalNetAmount' => 100,
                'departmentNumber' => 75,
            ],
        ],
    ];

    $result = $service->getBookedDepartment75Distribution('2026-01-01', '2026-01-31');

    expect($result['warnings'])->toBe([]);
    expect($result['collective_results']['booked_net_amount'])->toBe(100.0);
    expect($result['collective_results']['distributed_net_amount'])->toBe(100.0);
    expect($result['collective_results']['department_distribution']['3'])->toBe(100.0);
});
