<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Stock;
use InvalidArgumentException;

final class StockService extends BaseService
{
	public function __construct(
		private readonly Stock $stock = new Stock(),
		private readonly StockValuationService $valuation = new StockValuationService()
	) {
	}

    public function initialize(): int
    {
        return $this->stock->initializeMissingRows();
    }

    public function dashboard(): array
    {
        return [
            'summary' => $this->stock->summary(),
            'recent' => $this->stock->recentMovements(8),
        ];
    }

		public function entree(
			int $articleId,
			float $quantite,
			float $prixAchatHt,
			string $commentaire = ''
		): void {
			$quantite = round($quantite, 0);
			$prixAchatHt = round($prixAchatHt, 2);

			if ($articleId <= 0) {
				throw new InvalidArgumentException(
					'Article obligatoire.'
				);
			}

			if ($quantite <= 0) {
				throw new InvalidArgumentException(
					'La quantité doit être supérieure à zéro.'
				);
			}

			if ($prixAchatHt < 0) {
				throw new InvalidArgumentException(
					'Le prix d’achat ne peut pas être négatif.'
				);
			}

			$this->stock->transaction(
				function () use (
					$articleId,
					$quantite,
					$prixAchatHt,
					$commentaire
				): void {
					$article = $this->stock->findArticle($articleId);

					if (!$article) {
						throw new InvalidArgumentException(
							'Article introuvable.'
						);
					}

					$valuation = $this->valuation->calculateEntry(
						(float)($article['quantite'] ?? 0),
						(float)($article['valeur_stock'] ?? 0),
						$quantite,
						$prixAchatHt
					);

					$this->stock->createMovement([
						'article_id' => $articleId,
						'type_mouvement' => 'ENT',
						'quantite' => $quantite,
						'prix_achat_ht' => $prixAchatHt,
						'origine' => 'ENTREE_MANUELLE',
						'commentaire' => $commentaire !== ''
							? $commentaire
							: 'Entrée manuelle — PMP : ' .
								number_format(
									$valuation['pmp'],
									4,
									'.',
									''
								),
					]);

					$this->stock->updateStockValuation(
						$articleId,
						$valuation['quantity'],
						(float)($article['stock_minimum'] ?? 0),
						$valuation['value']
					);
				}
			);
		}

public function ajustement(
    int $articleId,
    float $stockReel,
    string $commentaire = ''
): float {
    $stockReel = round($stockReel, 0);

    if ($articleId <= 0) {
        throw new InvalidArgumentException(
            'Article obligatoire.'
        );
    }

    if ($stockReel < 0) {
        throw new InvalidArgumentException(
            'Le stock réel ne peut pas être négatif.'
        );
    }

    return $this->stock->transaction(
        function () use (
            $articleId,
            $stockReel,
            $commentaire
        ): float {
            $article = $this->stock->findArticle($articleId);

            if (!$article) {
                throw new InvalidArgumentException(
                    'Article introuvable.'
                );
            }

            $currentQuantity = round(
                (float)($article['quantite'] ?? 0),
                0
            );

            $currentValue = round(
                (float)($article['valeur_stock'] ?? 0),
                2
            );

            $currentPmp = $currentQuantity > 0
                ? round($currentValue / $currentQuantity, 4)
                : round(
                    (float)($article['prix_achat_ht'] ?? 0),
                    4
                );

            $difference = round(
                $stockReel - $currentQuantity,
                0
            );

            if ($difference == 0.0) {
                return 0.0;
            }

            $valuation = $this->valuation->calculateInventory(
                $stockReel,
                $currentPmp
            );

            $this->stock->createMovement([
                'article_id' => $articleId,
                'type_mouvement' => 'ADJ',
                'quantite' => $difference,
                'prix_achat_ht' => $currentPmp,
                'origine' => 'AJUSTEMENT',
                'commentaire' => $commentaire !== ''
                    ? $commentaire
                    : 'Ajustement manuel — PMP : ' .
                        number_format(
                            $currentPmp,
                            4,
                            '.',
                            ''
                        ),
            ]);

            $this->stock->updateStockValuation(
                $articleId,
                $valuation['quantity'],
                (float)($article['stock_minimum'] ?? 0),
                $valuation['value']
            );

            return $difference;
        }
    );
}


public function inventaireGlobal(
    array $stockReelParArticle,
    string $commentaire = ''
): int {
    if (empty($stockReelParArticle)) {
        throw new InvalidArgumentException(
            'Aucune ligne d’inventaire à enregistrer.'
        );
    }

    return $this->stock->transaction(
        function () use (
            $stockReelParArticle,
            $commentaire
        ): int {
            $rows = $this->stock->inventoryRows();

            if (empty($rows)) {
                throw new InvalidArgumentException(
                    'Aucun article actif à inventorier.'
                );
            }

            $prepared = [];
            $nbEcarts = 0;
            $totalEcartQuantite = 0.0;
            $valeurEcartHt = 0.0;

            foreach ($rows as $row) {
                $articleId = (int)$row['article_id'];

                if (
                    !array_key_exists(
                        (string)$articleId,
                        $stockReelParArticle
                    )
                    && !array_key_exists(
                        $articleId,
                        $stockReelParArticle
                    )
                ) {
                    continue;
                }

                $raw = $stockReelParArticle[(string)$articleId]
                    ?? $stockReelParArticle[$articleId]
                    ?? '';

                $stockReel = round(
                    $this->parseNumber($raw),
                    0
                );

                if ($stockReel < 0) {
                    throw new InvalidArgumentException(
                        'Le stock réel ne peut pas être négatif.'
                    );
                }

                $stockTheorique = round(
                    (float)($row['stock_theorique'] ?? 0),
                    0
                );

                $currentValue = round(
                    (float)($row['valeur_stock'] ?? 0),
                    2
                );

                $currentPmp = $stockTheorique > 0
                    ? round(
                        $currentValue / $stockTheorique,
                        4
                    )
                    : round(
                        (float)($row['prix_achat_ht'] ?? 0),
                        4
                    );

                $ecart = round(
                    $stockReel - $stockTheorique,
                    0
                );

                $valeurEcart = round(
                    $ecart * $currentPmp,
                    2
                );

                $valuation = $this->valuation->calculateInventory(
                    $stockReel,
                    $currentPmp
                );

                if ($ecart != 0.0) {
                    $nbEcarts++;
                    $totalEcartQuantite += $ecart;
                    $valeurEcartHt += $valeurEcart;
                }

                $prepared[] = [
                    'article_id' => $articleId,
                    'stock_theorique' => $stockTheorique,
                    'stock_reel' => $stockReel,
                    'ecart' => $ecart,
                    'prix_achat_ht' => $currentPmp,
                    'valeur_ecart_ht' => $valeurEcart,
                    'stock_minimum' => round(
                        (float)($row['stock_minimum'] ?? 0),
                        0
                    ),
                    'nouvelle_valeur_stock' =>
                        $valuation['value'],
                ];
            }

            if (empty($prepared)) {
                throw new InvalidArgumentException(
                    'Aucune quantité réelle n’a été saisie.'
                );
            }

            $inventoryId = $this->stock->createInventoryHeader([
                'commentaire' => $commentaire !== ''
                    ? $commentaire
                    : 'Inventaire stock',

                'nb_articles' => count($prepared),
                'nb_ecarts' => $nbEcarts,

                'total_ecart_quantite' => round(
                    $totalEcartQuantite,
                    0
                ),

                'valeur_ecart_ht' => round(
                    $valeurEcartHt,
                    2
                ),

                'utilisateur' => 'Admin',
            ]);

            foreach ($prepared as $line) {
                $inventoryLine = $line;
                unset(
                    $inventoryLine['nouvelle_valeur_stock']
                );

                $inventoryLine['inventaire_id'] = $inventoryId;

                $this->stock->createInventoryLine(
                    $inventoryLine
                );

                if ((float)$line['ecart'] == 0.0) {
                    continue;
                }

                $this->stock->createMovement([
                    'article_id' => $line['article_id'],
                    'type_mouvement' => 'INV',
                    'quantite' => $line['ecart'],
                    'prix_achat_ht' => $line['prix_achat_ht'],
                    'origine' => 'INVENTAIRE_STOCK',
                    'origine_id' => $inventoryId,
                    'commentaire' => $commentaire !== ''
                        ? $commentaire
                        : 'Inventaire stock',
                    'utilisateur' => 'Admin',
                ]);

                $this->stock->updateStockValuation(
                    (int)$line['article_id'],
                    (float)$line['stock_reel'],
                    (float)$line['stock_minimum'],
                    (float)$line['nouvelle_valeur_stock']
                );
            }

            return $nbEcarts;
        }
    );
}

    private function parseNumber(mixed $value): float
    {
        if (is_int($value) || is_float($value)) {
            return (float) $value;
        }
        $value = str_replace(["\xc2\xa0", ' ', ','], ['', '', '.'], (string) $value);
        return is_numeric($value) ? (float) $value : 0.0;
    }

public function sortie(
    int $articleId,
    float $quantite,
    string $origine = 'SORTIE_MANUELLE',
    ?int $origineId = null,
    string $commentaire = ''
): void {
    $quantite = round($quantite, 0);

    if ($articleId <= 0) {
        throw new InvalidArgumentException(
            'Article obligatoire.'
        );
    }

    if ($quantite <= 0) {
        throw new InvalidArgumentException(
            'La quantité doit être supérieure à zéro.'
        );
    }

    $this->stock->transaction(
        function () use (
            $articleId,
            $quantite,
            $origine,
            $origineId,
            $commentaire
        ): void {
            $article = $this->stock->findArticle($articleId);

            if (!$article) {
                throw new InvalidArgumentException(
                    'Article introuvable.'
                );
            }

            $valuation = $this->valuation->calculateExit(
                (float)($article['quantite'] ?? 0),
                (float)($article['valeur_stock'] ?? 0),
                $quantite
            );

            $this->stock->createMovement([
                'article_id' => $articleId,
                'type_mouvement' => 'SOR',
                'quantite' => -$quantite,
                'prix_achat_ht' => $valuation['pmp'],
                'origine' => $origine,
                'origine_id' => $origineId,
                'commentaire' => $commentaire,
            ]);

            $this->stock->updateStockValuation(
                $articleId,
                $valuation['quantity'],
                (float)($article['stock_minimum'] ?? 0),
                $valuation['value']
            );
        }
    );
}

	public function retour(
		int $articleId,
		float $quantite,
		string $origine = 'AVOIR',
		?int $origineId = null,
		string $commentaire = ''
	): void {
		$quantite = round($quantite, 0);

		if ($articleId <= 0) {
			throw new InvalidArgumentException(
				'Article obligatoire.'
			);
		}

		if ($quantite <= 0) {
			throw new InvalidArgumentException(
				'La quantité doit être supérieure à zéro.'
			);
		}

		$this->stock->transaction(
			function () use (
				$articleId,
				$quantite,
				$origine,
				$origineId,
				$commentaire
			): void {
				$article = $this->stock->findArticle($articleId);

				if (!$article) {
					throw new InvalidArgumentException(
						'Article introuvable.'
					);
				}

				$currentQuantity = round(
					(float)($article['quantite'] ?? 0),
					0
				);

				$currentValue = round(
					(float)($article['valeur_stock'] ?? 0),
					2
				);

				/*
				 * Un retour client réintègre le stock au PMP courant.
				 * Si le stock est vide, on utilise le prix d’achat de l’article.
				 */
				$currentPmp = $currentQuantity > 0
					? round($currentValue / $currentQuantity, 4)
					: round(
						(float)($article['prix_achat_ht'] ?? 0),
						4
					);

				$valuation = $this->valuation->calculateEntry(
					$currentQuantity,
					$currentValue,
					$quantite,
					$currentPmp
				);

				$this->stock->createMovement([
					'article_id' => $articleId,
					'type_mouvement' => 'RET',
					'quantite' => $quantite,
					'prix_achat_ht' => $currentPmp,
					'origine' => $origine,
					'origine_id' => $origineId,
					'commentaire' => $commentaire !== ''
						? $commentaire
						: 'Retour en stock — PMP : ' .
							number_format(
								$valuation['pmp'],
								4,
								'.',
								''
							),
				]);

				$this->stock->updateStockValuation(
					$articleId,
					$valuation['quantity'],
					(float)($article['stock_minimum'] ?? 0),
					$valuation['value']
				);
			}
		);
	}

    public function setMinimum(int $articleId, float $minimum): void
    {
        if ($minimum < 0) {
            throw new InvalidArgumentException('Le stock minimum ne peut pas être négatif.');
        }

        $this->stock->transaction(function () use ($articleId, $minimum): void {
            $article = $this->stock->findArticle($articleId);
            if (!$article) {
                throw new InvalidArgumentException('Article introuvable.');
            }
            $this->stock->updateStock($articleId, (float) ($article['quantite'] ?? 0), $minimum);
        });
    }
}
