<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Validator;
use App\Repositories\ArticleRepository;
use App\Repositories\CommandeFournisseurRepository;
use App\Repositories\FournisseurRepository;
use App\Repositories\ReceptionFournisseurRepository;

final class CommandeFournisseurService extends BaseService
{
    public function __construct(
        private readonly CommandeFournisseurRepository $commandes = new CommandeFournisseurRepository(),
        private readonly FournisseurRepository $fournisseurs = new FournisseurRepository(),
        private readonly ArticleRepository $articles = new ArticleRepository(),
        private readonly ReceptionFournisseurRepository $receptions = new ReceptionFournisseurRepository(),
    ) {
    }

    public function list(array $filters = []): array
    {
        return $this->commandes->all($filters);
    }

    public function formData(?int $id = null, ?int $fournisseurId = null): array
    {
        $commande = null;
        $lines = [];

        if ($id !== null) {
            $commande = $this->commandes->find($id);
            if (!$commande) {
                throw new \RuntimeException('Commande fournisseur introuvable.');
            }
            $lines = $this->commandes->lines($id);
        }

        return [
            'commande' => $commande ?? [
                'numero' => $this->commandes->nextNumber(),
                'fournisseur_id' => $fournisseurId ?: '',
                'date_commande' => date('Y-m-d'),
                'date_livraison_prevue' => '',
                'reference_fournisseur' => '',
                'statut' => 'brouillon',
                'observations' => '',
                'total_ht' => 0,
                'total_tva' => 0,
                'total_ttc' => 0,
            ],
            'lines' => $lines,
            'fournisseurs' => $this->fournisseurs->allActive(),
            'articles' => $this->articles->allActive(),
        ];
    }

    public function findWithLines(int $id): array
    {
        $commande = $this->commandes->find($id);
        if (!$commande) {
            throw new \RuntimeException('Commande fournisseur introuvable.');
        }

        return [
            'commande' => $commande,
            'lines' => $this->commandes->lines($id),
            'receptions' => $this->receptions->forCommand($id),
        ];
    }


    public function purchaseDashboard(): array
    {
        $analysis = $this->purchaseAnalysis(['marge_min' => 30]);

        $marginAlerts = [];
        $priceIncreaseAlerts = [];

        foreach ($analysis['rows'] as $row) {
            if (!empty($row['alerte_marge_faible'])) {
                $marginAlerts[] = $row;
            }
            if (!empty($row['alerte_hausse_achat'])) {
                $priceIncreaseAlerts[] = $row;
            }
        }

        return [
            'stats' => $this->commandes->purchaseDashboardStats(),
            'ordersInProgress' => $this->commandes->purchaseDashboardOrdersInProgress(),
            'expectedReceptions' => $this->commandes->purchaseDashboardExpectedReceptions(),
            'topSuppliers' => $this->commandes->purchaseDashboardTopSuppliers(),
            'topArticles' => $this->commandes->purchaseDashboardTopArticles(),
            'marginAlerts' => array_slice($marginAlerts, 0, 8),
            'priceIncreaseAlerts' => array_slice($priceIncreaseAlerts, 0, 8),
            'analysisStats' => $analysis['stats'],
        ];
    }

    public function finalisationCycle(array $filters = []): array
    {
        $normalized = [
            'q' => trim((string)($filters['q'] ?? '')),
            'fournisseur_id' => (int)($filters['fournisseur_id'] ?? 0),
            'etat' => trim((string)($filters['etat'] ?? '')),
        ];

        // La requête de rapprochement peut ainsi être utilisée dès la première
        // ouverture après une mise à niveau P2.6.3b.
        $this->receptions->ensureSchema();
        $rows = $this->commandes->finalisationCycle($normalized);
        $stats = [
            'total' => count($rows),
            'attente_reception' => 0,
            'a_facturer' => 0,
            'avec_ecart' => 0,
            'finalises' => 0,
        ];

        foreach ($rows as &$row) {
            $hasInvoice = (int)($row['nb_factures'] ?? 0) > 0;
            $isReceived = (string)($row['statut'] ?? '') === 'reçue';
            $invoiceGap = abs((float)($row['ecart_facture_commande'] ?? 0));

            if (!$isReceived) {
                $row['etat_cycle'] = 'attente_reception';
                $stats['attente_reception']++;
            } elseif (!$hasInvoice) {
                $row['etat_cycle'] = 'a_facturer';
                $stats['a_facturer']++;
            } elseif ($invoiceGap > 0.01) {
                $row['etat_cycle'] = 'avec_ecart';
                $stats['avec_ecart']++;
            } else {
                $row['etat_cycle'] = 'finalise';
                $stats['finalises']++;
            }
        }
        unset($row);

        return [
            'rows' => $rows,
            'stats' => $stats,
            'filters' => $normalized,
            'fournisseurs' => $this->fournisseurs->allActive(),
        ];
    }


    public function purchaseHistory(array $filters = []): array
    {
        $normalized = [
            'q' => trim((string)($filters['q'] ?? '')),
            'fournisseur_id' => (int)($filters['fournisseur_id'] ?? 0),
            'article_id' => (int)($filters['article_id'] ?? 0),
            'date_from' => trim((string)($filters['date_from'] ?? '')),
            'date_to' => trim((string)($filters['date_to'] ?? '')),
        ];

        return [
            'filters' => $normalized,
            'history' => $this->commandes->purchaseHistory($normalized),
            'stats' => $this->commandes->purchaseHistoryStats($normalized),
            'fournisseurs' => $this->fournisseurs->allActive(),
            'articles' => $this->articles->allActive(),
        ];
    }


    public function purchaseAnalysis(array $filters = []): array
    {
        $normalized = [
            'q' => trim((string)($filters['q'] ?? '')),
            'fournisseur_id' => (int)($filters['fournisseur_id'] ?? 0),
            'marge_min' => (float)str_replace(',', '.', (string)($filters['marge_min'] ?? 30)),
        ];
        if ($normalized['marge_min'] <= 0) {
            $normalized['marge_min'] = 30.0;
        }

        $rows = $this->commandes->purchaseAnalysis($normalized);
        $stats = [
            'nb_articles' => count($rows),
            'nb_hausses' => 0,
            'nb_marges_faibles' => 0,
            'total_achats_ht' => 0.0,
            'marge_moyenne' => 0.0,
        ];

        $marginSum = 0.0;
        $marginCount = 0;

        foreach ($rows as &$row) {
            $currentPurchase = (float)($row['prix_achat_ht'] ?? 0);
            $lastPurchase = (float)($row['dernier_prix_achat_ht'] ?? 0);
            $basePurchase = $lastPurchase > 0 ? $lastPurchase : $currentPurchase;
            $salePrice = (float)($row['prix_vente_ht'] ?? 0);
            $marginValue = round($salePrice - $basePurchase, 2);
            $marginRate = $salePrice > 0 ? round(($marginValue / $salePrice) * 100, 2) : 0.0;
            $purchaseGap = $lastPurchase > 0 ? round($lastPurchase - $currentPurchase, 2) : 0.0;
            $purchaseGapRate = ($currentPurchase > 0 && $lastPurchase > 0) ? round(($purchaseGap / $currentPurchase) * 100, 2) : 0.0;

            $row['prix_achat_reference'] = $basePurchase;
            $row['marge_reelle_dh'] = $marginValue;
            $row['marge_reelle_pourcentage'] = $marginRate;
            $row['ecart_prix_achat'] = $purchaseGap;
            $row['ecart_prix_achat_pourcentage'] = $purchaseGapRate;
            $row['alerte_hausse_achat'] = $purchaseGap > 0.001;
            $row['alerte_marge_faible'] = $salePrice > 0 && $marginRate < $normalized['marge_min'];

            if ($row['alerte_hausse_achat']) {
                $stats['nb_hausses']++;
            }
            if ($row['alerte_marge_faible']) {
                $stats['nb_marges_faibles']++;
            }
            $stats['total_achats_ht'] += (float)($row['total_achats_ht'] ?? 0);
            if ($salePrice > 0) {
                $marginSum += $marginRate;
                $marginCount++;
            }
        }
        unset($row);

        $stats['total_achats_ht'] = round($stats['total_achats_ht'], 2);
        $stats['marge_moyenne'] = $marginCount > 0 ? round($marginSum / $marginCount, 2) : 0.0;

        return [
            'filters' => $normalized,
            'rows' => $rows,
            'stats' => $stats,
            'fournisseurs' => $this->fournisseurs->allActive(),
        ];
    }

    public function create(array $data): int
    {
        $header = $this->normalizeHeader($data);
        if (empty($header['numero'])) {
            $header['numero'] = $this->commandes->nextNumber();
        }

        $lines = $this->normalizeLines($data);
        $this->validateBusiness($header, $lines);

        return $this->commandes->create($header, $lines);
    }

public function update(int $id, array $data): void
{
    if ($id <= 0) {
        throw new \RuntimeException(
            'Identifiant commande invalide.'
        );
    }

    $commande = $this->commandes->find($id);

    if (!$commande) {
        throw new \RuntimeException(
            'Commande fournisseur introuvable.'
        );
    }

    if ((int)($commande['actif'] ?? 1) !== 1) {
        throw new \RuntimeException(
            'Une commande archivée ne peut pas être modifiée.'
        );
    }

    $statut = (string)($commande['statut'] ?? '');

    if (in_array(
        $statut,
        ['partiellement_reçue', 'reçue'],
        true
    )) {
        throw new \RuntimeException(
            'Une commande ayant déjà fait l’objet d’une réception ne peut plus être modifiée.'
        );
    }

    /*
     * Sécurité complémentaire :
     * on contrôle directement les quantités reçues.
     */
    $existingLines = $this->commandes->lines($id);

    foreach ($existingLines as $line) {
        if ((float)($line['quantite_recue'] ?? 0) > 0) {
            throw new \RuntimeException(
                'Cette commande contient déjà des quantités réceptionnées et ne peut plus être modifiée.'
            );
        }
    }

    $header = $this->normalizeHeader($data);
    $lines = $this->normalizeLines($data);

    $this->validateBusiness($header, $lines);

    $this->commandes->update(
        $id,
        $header,
        $lines
    );
}

    public function receive(int $id, array $data): array
    {
        if ($id <= 0) {
            throw new \RuntimeException('Identifiant commande invalide.');
        }

        $received = $data['quantite_reception'] ?? [];
        if (!is_array($received)) {
            throw new \RuntimeException('Quantités reçues invalides.');
        }

        $date = trim((string)($data['date_reception'] ?? ''));
        if ($date === '') {
            throw new \RuntimeException('La date de réception est obligatoire.');
        }
        return $this->receptions->create($id, $data, $received);
    }

    public function receptionJournal(array $filters = []): array
    {
        $normalized = [
            'q' => trim((string)($filters['q'] ?? '')),
            'fournisseur_id' => (int)($filters['fournisseur_id'] ?? 0),
            'statut' => trim((string)($filters['statut'] ?? '')),
            'date_from' => trim((string)($filters['date_from'] ?? '')),
            'date_to' => trim((string)($filters['date_to'] ?? '')),
        ];
        return [
            'receptions' => $this->receptions->all($normalized),
            'filters' => $normalized,
            'fournisseurs' => $this->fournisseurs->allActive(),
        ];
    }

    public function nextReceptionNumber(): string
    {
        return $this->receptions->nextNumber();
    }

    public function cancelReception(int $id, string $reason): int
    {
        if ($id <= 0) {
            throw new \RuntimeException('Réception fournisseur invalide.');
        }
        return $this->receptions->cancel($id, $reason);
    }

    public function deactivate(int $id): void
    {
        if ($id <= 0) {
            throw new \RuntimeException('Identifiant commande invalide.');
        }
        $this->commandes->deactivate($id);
    }

    public function activate(int $id): void
    {
        if ($id <= 0 || !$this->commandes->find($id)) {
            throw new \RuntimeException('Commande fournisseur introuvable.');
        }
        $this->commandes->activate($id);
    }

    public function validator(array $data): Validator
    {
        return (new Validator())
            ->required('fournisseur_id', $data['fournisseur_id'] ?? '', 'Fournisseur')
            ->required('date_commande', $data['date_commande'] ?? '', 'Date de commande');
    }

    private function normalizeHeader(array $data): array
    {
        return [
            'numero' => trim((string)($data['numero'] ?? '')),
            'fournisseur_id' => (int)($data['fournisseur_id'] ?? 0),
            'date_commande' => trim((string)($data['date_commande'] ?? date('Y-m-d'))),
            'date_livraison_prevue' => trim((string)($data['date_livraison_prevue'] ?? '')),
            'reference_fournisseur' => trim((string)($data['reference_fournisseur'] ?? '')),
            'statut' => trim((string)($data['statut'] ?? 'brouillon')) ?: 'brouillon',
            'observations' => trim((string)($data['observations'] ?? '')),
        ];
    }

    private function normalizeLines(array $data): array
    {
        $articleIds = $data['article_id'] ?? [];
        $references = $data['reference'] ?? [];
        $designations = $data['designation'] ?? [];
        $quantites = $data['quantite'] ?? [];
        $prix = $data['prix_achat_ht'] ?? [];
        $tvas = $data['taux_tva'] ?? [];

        $count = max(count((array)$designations), count((array)$articleIds));
        $lines = [];

        for ($i = 0; $i < $count; $i++) {
            $designation = trim((string)($designations[$i] ?? ''));
            $quantity = (float)str_replace(',', '.', (string)($quantites[$i] ?? 0));
            $price = (float)str_replace(',', '.', (string)($prix[$i] ?? 0));

            if ($designation === '' && $quantity <= 0 && $price <= 0) {
                continue;
            }

            $lines[] = [
                'article_id' => (int)($articleIds[$i] ?? 0),
                'reference' => trim((string)($references[$i] ?? '')),
                'designation' => $designation,
                'quantite' => $quantity,
                'prix_achat_ht' => $price,
                'taux_tva' => (float)str_replace(',', '.', (string)($tvas[$i] ?? 20)),
            ];
        }

        return $lines;
    }

    private function validateBusiness(array $header, array $lines): void
    {
        $allowedStatuses = ['brouillon', 'validée', 'annulée'];
        if (!in_array((string)$header['statut'], $allowedStatuses, true)) {
            throw new \RuntimeException('Le statut de la commande est invalide.');
        }

        if ($header['fournisseur_id'] <= 0) {
            throw new \RuntimeException('Le fournisseur est obligatoire.');
        }

        if (empty($lines)) {
            throw new \RuntimeException('La commande doit contenir au moins une ligne.');
        }

        foreach ($lines as $line) {
            if (trim((string)$line['designation']) === '') {
                throw new \RuntimeException('Chaque ligne doit contenir une désignation.');
            }
            if ((float)$line['quantite'] <= 0) {
                throw new \RuntimeException('Chaque ligne doit contenir une quantité positive.');
            }
            if ((float)$line['prix_achat_ht'] < 0) {
                throw new \RuntimeException('Le prix d’achat HT ne peut pas être négatif.');
            }
        }
    }
}
