<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Validator;
use App\Repositories\FactureFournisseurRepository;
use App\Repositories\FournisseurRepository;

final class FactureFournisseurService extends BaseService
{
    public function __construct(
        private readonly FactureFournisseurRepository $factures = new FactureFournisseurRepository(),
        private readonly FournisseurRepository $fournisseurs = new FournisseurRepository(),
    ) {
    }

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

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

    public function echeancier(array $filters = []): array
    {
        $normalized = [
            'fournisseur_id' => (int)($filters['fournisseur_id'] ?? 0),
            'periode' => trim((string)($filters['periode'] ?? 'toutes')) ?: 'toutes',
            'date_debut' => trim((string)($filters['date_debut'] ?? '')),
            'date_fin' => trim((string)($filters['date_fin'] ?? '')),
        ];

        $factures = $this->factures->echeancier($normalized);
        $today = new \DateTimeImmutable(date('Y-m-d'));
        $inSevenDays = $today->modify('+7 days');
        $inThirtyDays = $today->modify('+30 days');

        $stats = [
            'total_reste_du' => 0.0,
            'total_retard' => 0.0,
            'total_7_jours' => 0.0,
            'total_30_jours' => 0.0,
            'total_sans_echeance' => 0.0,
            'nb_factures' => 0,
            'nb_retard' => 0,
        ];

        foreach ($factures as &$facture) {
            $reste = (float)($facture['reste_du'] ?? 0);
            $stats['total_reste_du'] += $reste;
            $stats['nb_factures']++;

            $dateEcheance = trim((string)($facture['date_echeance'] ?? ''));
            if ($dateEcheance === '') {
                $facture['etat_echeance'] = 'sans_echeance';
                $stats['total_sans_echeance'] += $reste;
                continue;
            }

            $due = new \DateTimeImmutable($dateEcheance);
            $facture['jours_ecart'] = (int)$today->diff($due)->format('%r%a');

            if ($due < $today) {
                $facture['etat_echeance'] = 'en_retard';
                $stats['total_retard'] += $reste;
                $stats['nb_retard']++;
            } elseif ($due <= $inSevenDays) {
                $facture['etat_echeance'] = 'sous_7_jours';
                $stats['total_7_jours'] += $reste;
                $stats['total_30_jours'] += $reste;
            } elseif ($due <= $inThirtyDays) {
                $facture['etat_echeance'] = 'sous_30_jours';
                $stats['total_30_jours'] += $reste;
            } else {
                $facture['etat_echeance'] = 'a_venir';
            }
        }
        unset($facture);

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


    public function dashboard(): array
    {
        $stats = $this->factures->dashboardStats();
        return [
            'stats' => $stats,
            'overdueFactures' => $this->factures->dashboardOverdueFactures(8),
            'upcomingFactures' => $this->factures->dashboardUpcomingFactures(8),
            'topFournisseursDue' => $this->factures->dashboardTopFournisseursDue(8),
            'recentReglements' => $this->factures->dashboardRecentReglements(8),
        ];
    }


    public function finalisation(): array
    {
        $dashboard = $this->dashboard();
        $stats = $dashboard['stats'] ?? [];

        $rules = [
            [
                'titre' => 'Création facture fournisseur',
                'etat' => 'Verrouillé',
                'detail' => 'Une facture fournisseur se crée depuis une commande fournisseur réceptionnée.',
            ],
            [
                'titre' => 'Montants',
                'etat' => 'Verrouillé',
                'detail' => 'Les montants HT, TVA et TTC sont repris depuis la commande et restent contrôlés.',
            ],
            [
                'titre' => 'Règlements',
                'etat' => 'Verrouillé',
                'detail' => 'Une facture peut recevoir plusieurs règlements sans dépasser le reste dû.',
            ],
            [
                'titre' => 'Statut automatique',
                'etat' => 'Verrouillé',
                'detail' => 'Le statut passe automatiquement en non réglée, partiellement réglée ou réglée.',
            ],
            [
                'titre' => 'Échéancier',
                'etat' => 'Verrouillé',
                'detail' => 'Les retards, paiements à venir et factures sans échéance sont suivis dans une page dédiée.',
            ],
            [
                'titre' => 'Tableau de bord',
                'etat' => 'Verrouillé',
                'detail' => 'Le total dû, les retards, les prochains paiements et les derniers règlements sont visibles en synthèse.',
            ],
        ];

        $links = [
            ['label' => 'Commandes fournisseurs', 'page' => 'commandes_fournisseurs', 'description' => 'Créer et suivre les commandes fournisseurs.'],
            ['label' => 'Factures fournisseurs', 'page' => 'factures_fournisseurs', 'description' => 'Consulter les factures, statuts et restes dus.'],
            ['label' => 'Échéancier fournisseurs', 'page' => 'factures_fournisseurs_echeancier', 'description' => 'Voir les factures à payer, retards et échéances.'],
            ['label' => 'Tableau fournisseurs', 'page' => 'factures_fournisseurs_dashboard', 'description' => 'Piloter les dettes fournisseurs et les règlements.'],
            ['label' => 'Cycle achats', 'page' => 'commandes_fournisseurs_finalisation', 'description' => 'Revoir le cycle achats complet.'],
        ];

        return $dashboard + [
            'rules' => $rules,
            'links' => $links,
            'version' => 'Phoenix P3.3.5',
            'total_du' => (float)($stats['total_du'] ?? 0),
            'total_retard' => (float)($stats['total_retard'] ?? 0),
            'total_regle_mois' => (float)($stats['total_regle_mois'] ?? 0),
            'nb_a_payer' => (int)($stats['nb_a_payer'] ?? 0),
        ];
    }

    public function formDataFromCommand(int $commandeId): array
    {
        $commande = $this->factures->findCommand($commandeId);
        if (!$commande) {
            throw new \RuntimeException('Commande fournisseur introuvable.');
        }

        if (!in_array((string)($commande['statut'] ?? ''), ['partiellement_reçue', 'reçue'], true)) {
            throw new \RuntimeException('La facture fournisseur doit être créée après réception de la commande.');
        }

        $existing = $this->factures->existingForCommand($commandeId);
        if ($existing) {
            throw new \RuntimeException('Une facture fournisseur existe déjà pour cette commande.');
        }

        $lignes = [];
        foreach ($this->factures->commandLines($commandeId) as $ligne) {
            $quantite = (float)($ligne['quantite_recue'] ?? 0);
            if ($quantite <= 0) {
                continue;
            }
            $lignes[] = [
                'article_id' => (int)($ligne['article_id'] ?? 0),
                'type_ligne' => 'article',
                'reference' => (string)($ligne['reference'] ?? ''),
                'designation' => (string)($ligne['designation'] ?? ''),
                'quantite' => $quantite,
                'prix_achat_ht' => (float)($ligne['prix_achat_ht'] ?? 0),
                'taux_tva' => (float)($ligne['taux_tva'] ?? 0),
            ];
        }

        if (!$lignes) {
            throw new \RuntimeException('Aucune ligne réceptionnée ne peut être facturée pour cette commande.');
        }

        return [
            'commande' => $commande,
            'lignes' => $lignes,
            'facture' => [
                'numero_interne' => $this->factures->nextNumber(),
                'numero_facture_fournisseur' => '',
                'date_facture' => date('Y-m-d'),
                'date_echeance' => '',
                'montant_ht' => (float)$commande['total_ht'],
                'montant_tva' => (float)$commande['total_tva'],
                'montant_ttc' => (float)$commande['total_ttc'],
                'montant_regle' => 0,
                'observations' => '',
                'fournisseur_id' => (int)$commande['fournisseur_id'],
                'commande_fournisseur_id' => (int)$commande['id'],
            ],
        ];
    }

    public function formData(int $commandeId = 0): array
    {
        if ($commandeId > 0) return $this->formDataFromCommand($commandeId) + ['fournisseurs'=>$this->fournisseurs->allActive(), 'articles'=>$this->factures->articles()];
        return ['commande'=>null, 'fournisseurs'=>$this->fournisseurs->allActive(), 'articles'=>$this->factures->articles(), 'lignes'=>[], 'facture'=>[
            'id'=>0, 'numero_interne'=>$this->factures->nextNumber(), 'numero_facture_fournisseur'=>'', 'date_facture'=>date('Y-m-d'), 'date_echeance'=>'', 'fournisseur_id'=>0, 'commande_fournisseur_id'=>0, 'montant_ht'=>0, 'montant_tva'=>0, 'montant_ttc'=>0, 'montant_regle'=>0, 'observations'=>'', 'justificatif_nom'=>'', 'justificatif_fichier'=>''
        ]];
    }

    public function find(int $id): array
    {
        $facture = $this->factures->find($id);
        if (!$facture) {
            throw new \RuntimeException('Facture fournisseur introuvable.');
        }
        return ['facture' => $facture, 'lignes'=>$this->factures->lines($id), 'reglements' => $this->factures->reglements($id)];
    }

    public function editData(int $id): array
    {
        return $this->find($id) + ['fournisseurs'=>$this->fournisseurs->allActive(), 'articles'=>$this->factures->articles(), 'commande'=>null];
    }

public function create(array $data): int
{
    $normalized = $this->normalize($data);

    $commandeId = (int)$normalized['commande_fournisseur_id'];

    if ($commandeId <= 0) {
        $normalized['numero_interne'] = $this->factures->nextNumber();
        $normalized['montant_regle'] = 0.0;
        $normalized['lignes'] = $this->normalizeLines($data);
        $this->applyLineTotals($normalized);
        $this->applyUpload($normalized);
        $this->validateBusiness($normalized);
        return $this->factures->create($normalized);
    }

    /*
     * Rechargement obligatoire depuis la base :
     * les montants et le fournisseur transmis par le formulaire
     * ne sont jamais considérés comme fiables.
     */
    $commande = $this->factures->findCommand($commandeId);

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

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

    if (!in_array(
        $statutCommande,
        ['partiellement_reçue', 'reçue'],
        true
    )) {
        throw new \RuntimeException(
            'La facture fournisseur ne peut être créée qu’après réception de la commande.'
        );
    }

    if ($this->factures->existingForCommand($commandeId)) {
        throw new \RuntimeException(
            'Une facture fournisseur existe déjà pour cette commande.'
        );
    }

    /*
     * Données métier imposées depuis la commande.
     */
    $normalized['fournisseur_id'] =
        (int)$commande['fournisseur_id'];

    $normalized['commande_fournisseur_id'] =
        (int)$commande['id'];

    $normalized['numero_interne'] =
        $this->factures->nextNumber();

    $normalized['montant_ht'] =
        round((float)$commande['total_ht'], 2);

    $normalized['montant_tva'] =
        round((float)$commande['total_tva'], 2);

    $normalized['montant_ttc'] =
        round((float)$commande['total_ttc'], 2);

    /*
     * Aucun règlement fictif à la création.
     * Tout paiement devra passer par reglements_fournisseurs.
     */
    $normalized['montant_regle'] = 0.0;
    $normalized['lignes'] = $this->normalizeLines($data);
    $this->applyUpload($normalized);

    $this->validateBusiness($normalized);

    return $this->factures->create($normalized);
}

    public function update(int $id, array $data): void
    {
        $normalized=$this->normalize($data);
        $normalized['lignes']=$this->normalizeLines($data);
        $this->applyLineTotals($normalized);
        $this->applyUpload($normalized);
        $this->validateBusiness($normalized);
        $this->factures->update($id,$normalized);
    }

    private function normalizeLines(array $data): array
    {
        $out=[]; $names=$data['ligne_designation']??[];
        foreach ($names as $i=>$name) {
            $designation=trim((string)$name); if ($designation==='') continue;
            $q=max(0,(float)str_replace(',','.',(string)($data['ligne_quantite'][$i]??1)));
            $p=max(0,(float)str_replace(',','.',(string)($data['ligne_prix_ht'][$i]??0)));
            $t=max(0,(float)str_replace(',','.',(string)($data['ligne_tva'][$i]??0)));
            if ($q<=0) throw new \RuntimeException('La quantité de chaque ligne doit être positive.');
            $ht=round($q*$p,2); $tv=round($ht*$t/100,2);
            $out[]=['article_id'=>(int)($data['ligne_article_id'][$i]??0),'type_ligne'=>(($data['ligne_type'][$i]??'article')==='frais'?'frais':'article'),'reference'=>trim((string)($data['ligne_reference'][$i]??'')),'designation'=>$designation,'quantite'=>$q,'prix_achat_ht'=>$p,'taux_tva'=>$t,'total_ht'=>$ht,'total_tva'=>$tv,'total_ttc'=>round($ht+$tv,2)];
        }
        if (!$out) throw new \RuntimeException('Ajoutez au moins un article ou un frais à la facture.');
        return $out;
    }

    private function applyLineTotals(array &$data): void
    {
        $data['montant_ht']=round(array_sum(array_column($data['lignes'],'total_ht')),2);
        $data['montant_tva']=round(array_sum(array_column($data['lignes'],'total_tva')),2);
        $data['montant_ttc']=round($data['montant_ht']+$data['montant_tva'],2);
    }

    private function applyUpload(array &$data): void
    {
        $file=$_FILES['justificatif']??null;
        if (!$file || (int)($file['error']??UPLOAD_ERR_NO_FILE)===UPLOAD_ERR_NO_FILE) return;
        if ((int)$file['error']!==UPLOAD_ERR_OK || (int)$file['size']>10*1024*1024) throw new \RuntimeException('Justificatif invalide ou supérieur à 10 Mo.');
        $ext=strtolower(pathinfo((string)$file['name'],PATHINFO_EXTENSION));
        if (!in_array($ext,['pdf','jpg','jpeg','png'],true)) throw new \RuntimeException('Le justificatif doit être un PDF, JPG ou PNG.');
        $dir=dirname(__DIR__,2).'/storage/documents/factures-fournisseurs'; if (!is_dir($dir) && !mkdir($dir,0775,true) && !is_dir($dir)) throw new \RuntimeException('Impossible de créer le dossier des justificatifs.');
        $name='ff_'.date('Ymd_His').'_'.bin2hex(random_bytes(4)).'.'.$ext;
        if (!move_uploaded_file((string)$file['tmp_name'],$dir.'/'.$name)) throw new \RuntimeException('Impossible d’enregistrer le justificatif.');
        $data['justificatif_nom']=(string)$file['name']; $data['justificatif_fichier']=$name;
    }


    public function reglementFormData(int $factureId): array
    {
        $data = $this->find($factureId);
        if ((float)$data['facture']['reste_du'] <= 0) {
            throw new \RuntimeException('Cette facture fournisseur est déjà réglée.');
        }

        return $data + [
            'reglement' => [
                'date_reglement' => date('Y-m-d'),
                'montant' => (float)$data['facture']['reste_du'],
                'mode_reglement' => 'virement',
                'reference_reglement' => '',
                'observations' => '',
            ],
        ];
    }

    public function journalReglements(array $filters = []): array
    {
        $normalized = [
            'fournisseur_id' => (int)($filters['fournisseur_id'] ?? 0),
            'mode_reglement' => trim((string)($filters['mode_reglement'] ?? '')),
            'date_debut' => trim((string)($filters['date_debut'] ?? '')),
            'date_fin' => trim((string)($filters['date_fin'] ?? '')),
            'etat' => trim((string)($filters['etat'] ?? 'actifs')) ?: 'actifs',
        ];
        $reglements = $this->factures->journalReglements($normalized);
        $totaux = ['total' => 0.0, 'par_mode' => []];
        foreach ($reglements as $reglement) {
            if (!(int)$reglement['actif']) continue;
            $montant = (float)$reglement['montant'];
            $mode = (string)($reglement['mode_reglement'] ?: 'autre');
            $totaux['total'] += $montant;
            $totaux['par_mode'][$mode] = ($totaux['par_mode'][$mode] ?? 0) + $montant;
        }
        return ['filters' => $normalized, 'reglements' => $reglements, 'totaux' => $totaux, 'fournisseurs' => $this->fournisseurs->allActive()];
    }

    public function annulerReglement(int $id, string $motif = ''): int
    {
        if ($id <= 0) throw new \RuntimeException('Identifiant règlement invalide.');
        return $this->factures->annulerReglement($id, $motif);
    }

    public function addReglement(int $factureId, array $data): int
    {
        $normalized = $this->normalizeReglement($data);
        if ($factureId <= 0) {
            throw new \RuntimeException('Identifiant facture invalide.');
        }
        if ($normalized['date_reglement'] === '') {
            throw new \RuntimeException('Date de règlement obligatoire.');
        }
        if ($normalized['montant'] <= 0) {
            throw new \RuntimeException('Le montant du règlement doit être positif.');
        }

        return $this->factures->addReglement($factureId, $normalized);
    }

    public function reglementValidator(array $data): Validator
    {
        return (new Validator())
            ->required('date_reglement', $data['date_reglement'] ?? '', 'Date de règlement')
            ->required('montant', $data['montant'] ?? '', 'Montant');
    }

    private function normalizeReglement(array $data): array
    {
        $num = static fn($value): float => (float)str_replace(',', '.', str_replace(' ', '', (string)$value));

        return [
            'date_reglement' => trim((string)($data['date_reglement'] ?? date('Y-m-d'))),
            'montant' => $num($data['montant'] ?? 0),
            'mode_reglement' => trim((string)($data['mode_reglement'] ?? '')),
            'reference_reglement' => trim((string)($data['reference_reglement'] ?? '')),
            'observations' => trim((string)($data['observations'] ?? '')),
        ];
    }

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

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

    public function validator(array $data): Validator
    {
        return (new Validator())
            ->required('numero_facture_fournisseur', $data['numero_facture_fournisseur'] ?? '', 'Numéro facture fournisseur')
            ->required('date_facture', $data['date_facture'] ?? '', 'Date de facture');
    }

    private function normalize(array $data): array
    {
        $num = static fn($value): float => (float)str_replace(',', '.', str_replace(' ', '', (string)$value));

        return [
            'fournisseur_id' => (int)($data['fournisseur_id'] ?? 0),
            'commande_fournisseur_id' => (int)($data['commande_fournisseur_id'] ?? 0),
            'numero_interne' => trim((string)($data['numero_interne'] ?? '')) ?: $this->factures->nextNumber(),
            'numero_facture_fournisseur' => trim((string)($data['numero_facture_fournisseur'] ?? '')),
            'date_facture' => trim((string)($data['date_facture'] ?? date('Y-m-d'))),
            'date_echeance' => trim((string)($data['date_echeance'] ?? '')),
            'montant_ht' => $num($data['montant_ht'] ?? 0),
            'montant_tva' => $num($data['montant_tva'] ?? 0),
            'montant_ttc' => $num($data['montant_ttc'] ?? 0),
            'montant_regle' => $num($data['montant_regle'] ?? 0),
            'observations' => trim((string)($data['observations'] ?? '')),
        ];
    }

    private function validateBusiness(array $data): void
    {
        if ($data['fournisseur_id'] <= 0) {
            throw new \RuntimeException('Fournisseur obligatoire.');
        }
        if ($data['numero_facture_fournisseur'] === '') {
            throw new \RuntimeException('Numéro de facture fournisseur obligatoire.');
        }
        if ($data['date_facture'] === '') {
            throw new \RuntimeException('Date de facture obligatoire.');
        }
        if ($data['montant_ttc'] <= 0) {
            throw new \RuntimeException('Le montant TTC doit être positif.');
        }
        if ($data['montant_regle'] < 0) {
            throw new \RuntimeException('Le montant réglé ne peut pas être négatif.');
        }
        if ($data['montant_regle'] > $data['montant_ttc']) {
            throw new \RuntimeException('Le montant réglé ne peut pas dépasser le total TTC.');
        }
    }
}
