<?php
declare(strict_types=1);

namespace App\Modules\Settings;

use App\Core\Exceptions\IntegrationConfigException;
use App\Core\Http\ToggleableRepositoryTrait;
use PDO;
use RuntimeException;
use Throwable;

final class InvoiceConfigRepository
{
    use ToggleableRepositoryTrait;

    public function __construct(
        private readonly PDO $pdo,
        private readonly ?FakturowniaIntegrationRepository $fakturownia = null
    )
    {
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function listAll(): array
    {
        $statement = $this->pdo->prepare(
            'SELECT ic.*, i.name AS integration_name
             FROM invoice_configs ic
             LEFT JOIN integrations i ON i.id = ic.integration_id AND i.type = :type
             ORDER BY ic.is_active DESC, ic.name ASC'
        );
        $statement->execute(['type' => 'fakturownia']);
        $rows = $statement->fetchAll(PDO::FETCH_ASSOC);

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

    /**
     * @return array<string, mixed>|null
     */
    public function findById(int $id): ?array
    {
        if ($id <= 0) {
            return null;
        }

        $statement = $this->pdo->prepare(
            'SELECT ic.*, i.name AS integration_name
             FROM invoice_configs ic
             LEFT JOIN integrations i ON i.id = ic.integration_id AND i.type = :type
             WHERE ic.id = :id
             LIMIT 1'
        );
        $statement->execute(['id' => $id, 'type' => 'fakturownia']);
        $row = $statement->fetch(PDO::FETCH_ASSOC);

        return is_array($row) ? $row : null;
    }

    /**
     * @param array<string, mixed> $data
     * @return int Saved config id
     */
    public function save(array $data): int
    {
        $name = trim((string) ($data['name'] ?? ''));
        if ($name === '') {
            throw new IntegrationConfigException('Nazwa konfiguracji faktury jest wymagana.');
        }
        if (mb_strlen($name) > 128) {
            throw new IntegrationConfigException('Nazwa konfiguracji jest za dluga (max 128 znakow).');
        }

        $numberFormat = trim((string) ($data['number_format'] ?? ''));
        if ($numberFormat === '') {
            throw new IntegrationConfigException('Format numeracji jest wymagany.');
        }
        if (mb_strlen($numberFormat) > 64) {
            throw new IntegrationConfigException('Format numeracji jest za dlugi (max 64 znakow).');
        }

        $numberingType = (string) ($data['numbering_type'] ?? 'monthly');
        if (!in_array($numberingType, ['monthly', 'yearly'], true)) {
            $numberingType = 'monthly';
        }

        $saleDateSource = (string) ($data['sale_date_source'] ?? 'issue_date');
        if (!in_array($saleDateSource, ['order_date', 'payment_date', 'issue_date'], true)) {
            $saleDateSource = 'issue_date';
        }

        $orderReference = (string) ($data['order_reference'] ?? 'none');
        if (!in_array($orderReference, ['none', 'orderpro', 'integration'], true)) {
            $orderReference = 'none';
        }

        $paymentToDays = (int) ($data['payment_to_days'] ?? 7);
        if ($paymentToDays < 0) {
            $paymentToDays = 0;
        }
        if ($paymentToDays > 365) {
            $paymentToDays = 365;
        }

        $defaultKind = trim((string) ($data['default_kind'] ?? 'vat'));
        if ($defaultKind === '') {
            $defaultKind = 'vat';
        }
        if (mb_strlen($defaultKind) > 32) {
            throw new IntegrationConfigException('Typ dokumentu jest za dlugi (max 32 znakow).');
        }

        $isDelegated = !empty($data['is_delegated']) ? 1 : 0;
        $isActive = !empty($data['is_active']) ? 1 : 0;
        $integrationId = isset($data['integration_id']) && $data['integration_id'] !== ''
            ? (int) $data['integration_id']
            : null;

        if ($isDelegated === 1) {
            $integrationId = $this->globalFakturowniaIntegrationId();
            if ($integrationId <= 0) {
                throw new IntegrationConfigException(
                    'Przed delegacja faktur skonfiguruj globalna integracje Fakturownia.'
                );
            }
            if (!$this->isFakturowniaIntegration($integrationId)) {
                throw new IntegrationConfigException(
                    'Wybrana integracja nie jest kontem Fakturowni - sprawdz konfiguracje.'
                );
            }
        } else {
            $integrationId = null;
        }

        $params = [
            'name' => $name,
            'integration_id' => $integrationId,
            'is_delegated' => $isDelegated,
            'is_active' => $isActive,
            'number_format' => $numberFormat,
            'numbering_type' => $numberingType,
            'sale_date_source' => $saleDateSource,
            'order_reference' => $orderReference,
            'payment_to_days' => $paymentToDays,
            'default_kind' => $defaultKind,
        ];

        $id = isset($data['id']) && $data['id'] !== '' ? (int) $data['id'] : 0;

        if ($id > 0) {
            $statement = $this->pdo->prepare(
                'UPDATE invoice_configs SET
                    name = :name,
                    integration_id = :integration_id,
                    is_delegated = :is_delegated,
                    is_active = :is_active,
                    number_format = :number_format,
                    numbering_type = :numbering_type,
                    sale_date_source = :sale_date_source,
                    order_reference = :order_reference,
                    payment_to_days = :payment_to_days,
                    default_kind = :default_kind,
                    updated_at = NOW()
                WHERE id = :id'
            );
            $params['id'] = $id;
            $statement->execute($params);
            return $id;
        }

        $statement = $this->pdo->prepare(
            'INSERT INTO invoice_configs
                (name, integration_id, is_delegated, is_active, number_format, numbering_type,
                 sale_date_source, order_reference, payment_to_days, default_kind)
            VALUES
                (:name, :integration_id, :is_delegated, :is_active, :number_format, :numbering_type,
                 :sale_date_source, :order_reference, :payment_to_days, :default_kind)'
        );
        $statement->execute($params);

        return (int) $this->pdo->lastInsertId();
    }

    public function toggleStatus(int $id): void
    {
        $this->toggleActive('invoice_configs', $id);
    }

    public function delete(int $id): void
    {
        if ($id <= 0) {
            throw new IntegrationConfigException('Nieprawidlowy identyfikator konfiguracji.');
        }

        if ($this->hasInvoices($id)) {
            throw new IntegrationConfigException(
                'Nie mozna usunac konfiguracji - istnieja juz wystawione faktury powiazane z ta konfiguracja.'
            );
        }

        $statement = $this->pdo->prepare('DELETE FROM invoice_configs WHERE id = :id');
        $statement->execute(['id' => $id]);
    }

    private function isFakturowniaIntegration(int $integrationId): bool
    {
        try {
            $statement = $this->pdo->prepare(
                'SELECT 1 FROM integrations WHERE id = :id AND type = :type LIMIT 1'
            );
            $statement->execute(['id' => $integrationId, 'type' => 'fakturownia']);
            return $statement->fetchColumn() !== false;
        } catch (Throwable) {
            return false;
        }
    }

    private function globalFakturowniaIntegrationId(): int
    {
        if ($this->fakturownia !== null) {
            return $this->fakturownia->getIntegrationId();
        }

        try {
            $statement = $this->pdo->prepare(
                'SELECT id FROM integrations WHERE type = :type ORDER BY id ASC LIMIT 1'
            );
            $statement->execute(['type' => 'fakturownia']);
            $value = $statement->fetchColumn();
        } catch (Throwable) {
            return 0;
        }

        return is_numeric($value) ? (int) $value : 0;
    }

    private function hasInvoices(int $configId): bool
    {
        try {
            $statement = $this->pdo->prepare(
                'SELECT 1 FROM invoices WHERE config_id = :id LIMIT 1'
            );
            $statement->execute(['id' => $configId]);
            return $statement->fetchColumn() !== false;
        } catch (Throwable) {
            return false;
        }
    }
}
