<?php

declare(strict_types=1);

namespace App\Services;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Database\Capsule\Manager as DB;

class PrestashopService
{
    private Client $http;
    private string $psUrl;    // nouvelle Admin API  : https://shop.../admin-api
    private string $wsUrl;    // ancienne Webservice  : https://shop.../api
    private string $wsKey;    // clé webservice PS
    private string $clientId;
    private string $clientSecret;
    private const MAX_FAILURES  = 5;
    private const PAUSE_SECONDS = 60;

    public function __construct()
    {
        $shopBase           = rtrim($_ENV['PRESTASHOP_SHOP_URL'] ?? 'https://shop.mykollector.com', '/');
        $this->psUrl        = rtrim($_ENV['PRESTASHOP_URL'] ?? $shopBase . '/admin-api', '/');
        $this->wsUrl        = $shopBase . '/api';
        $this->wsKey        = $_ENV['PRESTASHOP_WS_KEY'] ?? '';
        $this->clientId     = $_ENV['PRESTASHOP_CLIENT_ID'] ?? '';
        $this->clientSecret = $_ENV['PRESTASHOP_CLIENT_SECRET'] ?? '';
        $this->http         = new Client(['timeout' => 30]);
    }

    // ─── Circuit breaker STOCKÉ EN BASE (OVH stateless — propriété d'instance inutile) ───

    private function getFailureCount(): int
    {
        $config = DB::table('prestashop_config')->first();
        return (int)($config->circuit_failure_count ?? 0);
    }

    private function incrementFailure(): void
    {
        $count = $this->getFailureCount() + 1;
        DB::table('prestashop_config')->where('id', 1)->update([
            'circuit_failure_count' => $count,
            'circuit_last_failure'  => date('Y-m-d H:i:s'),
        ]);

        if ($count >= self::MAX_FAILURES) {
            DB::table('prestashop_config')->where('id', 1)->update([
                'circuit_pause_until'   => date('Y-m-d H:i:s', time() + self::PAUSE_SECONDS),
                'circuit_failure_count' => 0,
            ]);
            error_log('[PS CircuitBreaker] Pause ' . self::PAUSE_SECONDS . 's activée après ' . self::MAX_FAILURES . ' échecs');
        }
    }

    private function resetFailures(): void
    {
        DB::table('prestashop_config')->where('id', 1)->update([
            'circuit_failure_count' => 0,
            'circuit_pause_until'   => null,
        ]);
    }

    private function isCircuitOpen(): bool
    {
        $config = DB::table('prestashop_config')->first();
        if (!$config || !$config->circuit_pause_until) return false;
        return strtotime($config->circuit_pause_until) > time();
    }

    // ─── OAuth2 Token ───

    public function getAccessToken(): ?string
    {
        if ($this->isCircuitOpen()) {
            throw new \Exception('Circuit breaker actif — PS temporairement indisponible');
        }

        $config = DB::table('prestashop_config')->first();
        if ($config && $config->access_token && $config->token_expires > time() + 60) {
            return $config->access_token;
        }

        try {
            $response = $this->http->post($this->psUrl . '/access_token', [
                'form_params' => [
                    'grant_type'    => 'client_credentials',
                    'client_id'     => $this->clientId,
                    'client_secret' => $this->clientSecret,
                ]
            ]);

            $data      = json_decode((string)$response->getBody(), true);
            $token     = $data['access_token'] ?? null;
            $expiresIn = $data['expires_in'] ?? 3600;

            if ($token) {
                DB::table('prestashop_config')->updateOrInsert(
                    ['id' => 1],
                    [
                        'access_token'          => $token,
                        'token_expires'         => time() + $expiresIn - 60,
                        'circuit_pause_until'   => null,
                        'circuit_failure_count' => 0,
                    ]
                );
                return $token;
            }

            $this->incrementFailure();
            return null;

        } catch (RequestException $e) {
            $this->incrementFailure();
            throw new \Exception('PS OAuth2 échec: ' . $e->getMessage());
        }
    }

    // ─── Webservice API (ancienne API PS — clé WS) — lecture commandes/clients ───

    private function wsRequest(string $endpoint, array $query = []): ?array
    {
        try {
            $query['output_format'] = 'JSON';
            $response = $this->http->get($this->wsUrl . '/' . ltrim($endpoint, '/'), [
                'auth'  => [$this->wsKey, ''],
                'query' => $query,
            ]);
            $this->resetFailures();
            return json_decode((string)$response->getBody(), true);
        } catch (RequestException $e) {
            $this->incrementFailure();
            error_log('[PS WS] ' . $endpoint . ' : ' . $e->getMessage());
            return null;
        }
    }

    // ─── Admin API OAuth2 (PS9) — push statuts uniquement ───

    private function apiRequest(string $method, string $endpoint, array $options = []): ?array
    {
        $token = $this->getAccessToken();
        if (!$token) return null;

        try {
            $response = $this->http->request($method, $this->psUrl . '/' . ltrim($endpoint, '/'), array_merge([
                'headers' => ['Authorization' => 'Bearer ' . $token],
            ], $options));

            $this->resetFailures();
            return json_decode((string)$response->getBody(), true);

        } catch (RequestException $e) {
            $this->incrementFailure();
            error_log('[PS API] ' . $method . ' ' . $endpoint . ' : ' . $e->getMessage());
            return null;
        }
    }

    // ─── Commandes PS ───

    public function getCommandesStatut2(): array
    {
        $data = $this->wsRequest('orders', ['filter[current_state]' => 2, 'display' => 'full', 'limit' => 100]);
        return $data['orders'] ?? [];
    }

    public function getCommandesB2b(): array
    {
        // Récupérer commandes statut 2 puis filtrer celles dont le client est dans le groupe B2B
        $groupId   = (int)($_ENV['GROUP_B2B_ID'] ?? 4);
        $commandes = $this->getCommandesStatut2();
        $b2b       = [];

        foreach ($commandes as $order) {
            $customer = $this->getClient((int)($order['id_customer'] ?? 0));
            if ($customer && (int)($customer['id_default_group'] ?? 0) === $groupId) {
                $b2b[] = $order;
            }
        }
        return $b2b;
    }

    public function getCommandeDetail(int $orderId): ?array
    {
        $data = $this->wsRequest("orders/{$orderId}", ['display' => 'full']);
        return $data['order'] ?? null;
    }

    public function getClient(int $customerId): ?array
    {
        $data = $this->wsRequest("customers/{$customerId}", ['display' => 'full']);
        return $data['customer'] ?? null;
    }

    private function getPsDb(): \PDO
    {
        $host   = $_ENV['PS_DB_HOST']   ?? '';
        $name   = $_ENV['PS_DB_NAME']   ?? '';
        $user   = $_ENV['PS_DB_USER']   ?? '';
        $pass   = $_ENV['PS_DB_PASS']   ?? '';
        $dsn    = "mysql:host={$host};dbname={$name};charset=utf8mb4";
        return new \PDO($dsn, $user, $pass, [
            \PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
            \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
        ]);
    }

    public function createCustomer(string $email, string $prenom, string $nom, string $passwordClair): ?int
    {
        $prefix = $_ENV['PS_TABLE_PREFIX'] ?? 'ps_';
        $pdo    = $this->getPsDb();

        // Vérifier si email déjà pris dans PS
        $stmt = $pdo->prepare("SELECT id_customer FROM {$prefix}customer WHERE email = ? AND deleted = 0 LIMIT 1");
        $stmt->execute([$email]);
        $existing = $stmt->fetch();
        if ($existing) {
            return (int)$existing['id_customer'];
        }

        // Hash PS compatible (md5 + salt like PS8/9 or use blowfish)
        // PS9 accepte bcrypt via password_hash
        $hashedPassword = password_hash($passwordClair, PASSWORD_BCRYPT);
        $now = date('Y-m-d H:i:s');
        $secure_key = md5(uniqid((string)rand(), true));

        $stmt = $pdo->prepare("
            INSERT INTO {$prefix}customer
                (id_shop_group, id_shop, id_gender, id_default_group, id_risk,
                 firstname, lastname, email, passwd, secure_key,
                 active, deleted, is_guest, newsletter, optin,
                 outstanding_allow_amount, show_public_prices, max_payment_days,
                 date_add, date_upd)
            VALUES
                (1, 1, 1, 3, 1,
                 ?, ?, ?, ?, ?,
                 1, 0, 0, 0, 0,
                 0, 0, 60,
                 ?, ?)
        ");
        $stmt->execute([$prenom, $nom, $email, $hashedPassword, $secure_key, $now, $now]);
        $newId = (int)$pdo->lastInsertId();

        // Associer le client au groupe "Client" (id=3) dans ps_customer_group
        // PS exige cette entrée sinon le client n'a aucun groupe effectif
        if ($newId > 0) {
            $stmtGrp = $pdo->prepare("
                INSERT IGNORE INTO {$prefix}customer_group (id_customer, id_group)
                VALUES (?, 3)
            ");
            $stmtGrp->execute([$newId]);
        }

        return $newId;
    }

    public function pushStatutCommande(int $orderId, int $statutId): bool
    {
        $result = $this->apiRequest('POST', 'order_histories', [
            'json' => ['order_history' => [
                'id_order' => $orderId, 'id_order_state' => $statutId,
            ]]
        ]);
        return $result !== null;
    }

    public function testerConnexion(): array
    {
        try {
            $token = $this->getAccessToken();
            if (!$token) return ['connected' => false, 'error' => 'Impossible d\'obtenir un token'];
            $orders = $this->getCommandesStatut2();
            return ['connected' => true, 'token_ok' => true, 'test_query' => count($orders) . ' commandes récupérées'];
        } catch (\Exception $e) {
            return ['connected' => false, 'error' => $e->getMessage()];
        }
    }

    public function testerProduit(int $productId): array
    {
        try {
            $data = $this->wsRequest("products/{$productId}", ['display' => 'full']);
            if (!$data || !isset($data['product'])) {
                return ['found' => false];
            }
            $p = $data['product'];
            // Lire les features mko_*
            $features = $p['associations']['product_features'] ?? $p['features'] ?? [];
            $mkoFeatures = [];
            foreach ($features as $f) {
                $fname = $f['name'] ?? '';
                $fval  = $f['value'] ?? '';
                // Si pas de name directement, lire via product_feature_values
                if (!$fname && isset($f['id'])) {
                    $fdata = $this->wsRequest("product_features/{$f['id']}");
                    $fname = $fdata['product_feature']['name'][0]['value'] ?? '';
                }
                if (str_starts_with($fname, 'mko_')) {
                    $mkoFeatures[$fname] = $fval;
                }
            }
            $nom = '';
            if (isset($p['name'])) {
                $nom = is_array($p['name']) ? ($p['name'][0]['value'] ?? '') : $p['name'];
            }
            $prix = $p['price'] ?? null;
            return [
                'found'           => true,
                'nom'             => $nom,
                'prix'            => $prix,
                'mko_parcours_id' => $mkoFeatures['mko_parcours_id'] ?? null,
                'mko_depart_lat'  => $mkoFeatures['mko_depart_lat']  ?? null,
                'mko_depart_lng'  => $mkoFeatures['mko_depart_lng']  ?? null,
                'mko_sommet_lat'  => $mkoFeatures['mko_sommet_lat']  ?? null,
                'mko_sommet_lng'  => $mkoFeatures['mko_sommet_lng']  ?? null,
            ];
        } catch (\Exception $e) {
            return ['found' => false, 'error' => $e->getMessage()];
        }
    }

    // ─── Import logique ───

    public function syncCommandes(GravureService $gravureService, MailService $mailService, bool $b2bOnly = false): array
    {
        $importees = 0;
        $ignorees  = 0;
        $erreurs   = 0;

        $commandes = $b2bOnly ? $this->getCommandesB2b() : $this->getCommandesStatut2();

        foreach ($commandes as $psOrder) {
            $orderId = (int)($psOrder['id'] ?? 0);
            if (!$orderId) continue;

            if (DB::table('commandes')->where('presta_order_id', $orderId)->exists()) {
                $ignorees++;
                continue;
            }

            try {
                $detail   = $this->getCommandeDetail($orderId);
                if (!$detail) { $erreurs++; continue; }

                $customer = $this->getClient((int)($detail['id_customer'] ?? 0));
                $email    = $customer['email'] ?? null;
                if (!$email) { $erreurs++; continue; }

                $user   = DB::table('users')->where('email', $email)->first();
                $userId = $user ? $user->id : null;

                if (!$userId) {
                    $userId = DB::table('users')->insertGetId([
                        'email'              => $email,
                        'prenom'             => $customer['firstname'] ?? null,
                        'nom'                => $customer['lastname'] ?? null,
                        'role'               => 'sportif',
                        'presta_customer_id' => (int)($detail['id_customer'] ?? 0),
                        'actif'              => 1,
                        'date_inscription'   => date('Y-m-d H:i:s'),
                    ]);
                }

                $isB2b        = ((int)($customer['id_default_group'] ?? 0)) === (int)($_ENV['GROUP_B2B_ID'] ?? 4);
                $typeCommande = $isB2b ? 'b2b_vierge' : 'medaille_complete';

                $parcoursSlug = $this->extractFeature($detail, 'mko_parcours_id');
                $produit      = $parcoursSlug ? DB::table('produits')->where('slug', $parcoursSlug)->first() : null;

                if (!$produit) {
                    $firstProductId = (int)($detail['associations']['order_rows'][0]['product_id'] ?? 0);
                    if ($firstProductId === 4) $typeCommande = 'gravure_seule';
                }

                $texteGravure      = null;
                $validationSportif = 0;
                $chrono            = null;

                if ($typeCommande === 'medaille_complete' && $userId && $produit) {
                    $chrono  = DB::table('chronos')
                        ->where('user_id', $userId)->where('produit_id', $produit->id)
                        ->where('statut', 'valide')->orderBy('created_at', 'desc')->first();

                    if ($chrono) {
                        $userObj = DB::table('users')->find($userId);
                        if ($userObj->prenom && $userObj->nom) {
                            $texteGravure = $gravureService->genererTexte('medaille_complete', [
                                'prenom'       => $userObj->prenom,
                                'nom'          => $userObj->nom,
                                'parcours_nom' => $produit->nom,
                                'date_debut'   => $chrono->date_debut,
                                'duree'        => $chrono->duree_secondes,
                            ]);
                        }
                    }
                } elseif ($typeCommande === 'gravure_seule') {
                    $customizations    = $this->extractCustomizations($detail);
                    $texteGravure      = $gravureService->genererTexte('gravure_seule', $customizations);
                    $validationSportif = 1; // Auto-validé — saisie PS = accord sportif
                } elseif ($isB2b) {
                    $texteGravure      = $gravureService->genererTexte('b2b_vierge', [
                        'b2b_client_nom' => $customer['company'] ?? ($customer['lastname'] ?? 'Client B2B'),
                        'parcours_nom'   => $produit->nom ?? 'PARCOURS',
                    ]);
                    $validationSportif = 1;
                }

                $commandeId = DB::table('commandes')->insertGetId([
                    'presta_order_id'    => $orderId,
                    'presta_order_ref'   => $detail['reference'] ?? ('MKO' . str_pad($orderId, 4, '0', STR_PAD_LEFT)),
                    'user_id'            => $userId,
                    'produit_id'         => $produit->id ?? null,
                    'chrono_id'          => $chrono->id ?? null,
                    'statut'             => 'importee',
                    'type_commande'      => $typeCommande,
                    'is_b2b'             => $isB2b ? 1 : 0,
                    'texte_gravure'      => $texteGravure,
                    'validation_sportif' => $validationSportif,
                    'created_at'         => date('Y-m-d H:i:s'),
                ]);

                DB::table('logs_audit')->insert([
                    'action'    => 'commande_importee',
                    'entite'    => 'commande',
                    'entite_id' => $commandeId,
                    'details'   => json_encode(['presta_order_id' => $orderId, 'type' => $typeCommande]),
                    'created_at'=> date('Y-m-d H:i:s'),
                ]);

                if ($texteGravure && $validationSportif === 0 && $email) {
                    $userObj = DB::table('users')->find($userId);
                    $mailService->commandeEnGravure(
                        $email,
                        $userObj->prenom ?? 'Sportif',
                        $texteGravure,
                        'MKO' . str_pad($orderId, 4, '0', STR_PAD_LEFT),
                        'https://chrono.mykollector.com/dashboard.html'
                    );
                }

                $importees++;

            } catch (\Exception $e) {
                error_log('[PS Sync] Erreur commande ' . $orderId . ': ' . $e->getMessage());
                DB::table('ps_import_queue')->insertOrIgnore([
                    'presta_order_id'     => $orderId,
                    'tentatives'          => 1,
                    'derniere_tentative'  => date('Y-m-d H:i:s'),
                    'prochaine_tentative' => date('Y-m-d H:i:s', time() + 600),
                    'statut'              => 'en_attente',
                    'erreur'              => $e->getMessage(),
                ]);
                $erreurs++;
            }
        }

        return compact('importees', 'ignorees', 'erreurs');
    }

    private function extractFeature(array $order, string $featureName): ?string
    {
        foreach ($order['associations']['product_features'] ?? [] as $f) {
            if (($f['name'] ?? '') === $featureName) return $f['value'] ?? null;
        }
        return null;
    }

    private function extractCustomizations(array $order): array
    {
        $result = [];
        foreach ($order['associations']['product_customizations'] ?? [] as $c) {
            $result[$c['type'] ?? 'field_' . ($c['index'] ?? 0)] = $c['value'] ?? '';
        }
        return $result;
    }

    /**
     * Soft-delete d'un client PrestaShop (deleted = 1, active = 0).
     * On ne supprime pas physiquement pour préserver l'historique des commandes.
     * Retourne true si le client a bien été trouvé et mis à jour, false sinon.
     */
    public function deleteCustomer(int $prestashopCustomerId): bool
    {
        try {
            $pdo    = $this->getPdoPresta();
            $prefix = $_ENV['PS_DB_PREFIX'] ?? 'ps_';

            $stmt = $pdo->prepare(
                "UPDATE {$prefix}customer
                 SET deleted = 1, active = 0
                 WHERE id_customer = ?
                 LIMIT 1"
            );
            $stmt->execute([$prestashopCustomerId]);

            return $stmt->rowCount() > 0;
        } catch (\Throwable $e) {
            // On log mais on ne bloque pas la suppression côté MKO
            error_log('[PS] deleteCustomer(' . $prestashopCustomerId . ') failed: ' . $e->getMessage());
            return false;
        }
    }
}
