<?php

declare(strict_types=1);

namespace App\Services;

use Stripe\Stripe;
use Stripe\StripeClient;
use Stripe\Account;
use Stripe\Exception\ApiErrorException;
use AppConfig;
use Database;
use PDO;
use Exception;
use App\Services\LogService;
use App\Services\ApiService;

/**
 * Service principal pour gérer l'intégration Stripe
 * Gère Stripe Connect, Terminal et les paiements
 */
class StripeService {
    private static ?self $instance = null;
    private StripeClient $stripe;
    private AppConfig $config;
    private PDO $db;
    private bool $testMode;
    
    private function __construct() {
        $this->config = AppConfig::getInstance();
        $this->db = Database::getInstance();
        
        // Déterminer le mode (test ou live)
        $stripeConfig = $this->config->getStripeConfig();
        $this->testMode = ($stripeConfig['mode'] ?? 'test') === 'test';
        
        // Initialiser Stripe avec la bonne clé
        $secretKey = $this->testMode 
            ? $stripeConfig['secret_key_test'] 
            : $stripeConfig['secret_key_live'];
            
        if (empty($secretKey) || strpos($secretKey, 'XXXX') !== false) {
            throw new Exception('Clé Stripe non configurée. Veuillez configurer vos clés dans AppConfig.php');
        }
        
        $this->stripe = new StripeClient([
            'api_key' => $secretKey,
            'stripe_version' => $stripeConfig['api_version']
        ]);
        
        // Définir la clé API globalement aussi (pour certaines opérations)
        Stripe::setApiKey($secretKey);
        Stripe::setApiVersion($stripeConfig['api_version']);
    }
    
    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    /**
     * Créer un compte Stripe Connect Express pour une amicale
     */
    public function createConnectAccount(int $entiteId): array {
        try {
            // Récupérer les infos de l'entité
            $stmt = $this->db->prepare(
                "SELECT * FROM entites WHERE id = :id"
            );
            $stmt->execute(['id' => $entiteId]);
            $entite = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$entite) {
                throw new Exception("Entité non trouvée");
            }
            
            // Vérifier si un compte existe déjà
            $stmt = $this->db->prepare(
                "SELECT * FROM stripe_accounts WHERE fk_entite = :fk_entite"
            );
            $stmt->execute(['fk_entite' => $entiteId]);
            $existingAccount = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if ($existingAccount) {
                // Si le compte existe, vérifier s'il est complet
                try {
                    $stripeAccount = $this->stripe->accounts->retrieve($existingAccount['stripe_account_id']);

                    // Si pas de location_id, créer la Location maintenant
                    $locationId = $existingAccount['stripe_location_id'];
                    if (empty($locationId)) {
                        try {
                            // Déchiffrer le nom pour la Location
                            $nom = !empty($entite['encrypted_name']) ? ApiService::decryptData($entite['encrypted_name']) : 'Amicale';

                            // Construire l'adresse
                            $adresse1 = !empty($entite['adresse1']) ? $entite['adresse1'] : 'Adresse non renseignée';
                            $adresse2 = !empty($entite['adresse2']) ? $entite['adresse2'] : '';
                            $ville = !empty($entite['ville']) ? $entite['ville'] : 'Ville';
                            $codePostal = !empty($entite['code_postal']) ? $entite['code_postal'] : '00000';

                            $location = $this->stripe->terminal->locations->create([
                                'display_name' => $nom,
                                'address' => [
                                    'line1' => $adresse1,
                                    'line2' => $adresse2,
                                    'city' => $ville,
                                    'postal_code' => $codePostal,
                                    'country' => 'FR',
                                ],
                                'metadata' => [
                                    'entite_id' => $entiteId,
                                    'type' => 'tap_to_pay'
                                ]
                            ], [
                                'stripe_account' => $existingAccount['stripe_account_id']
                            ]);

                            $locationId = $location->id;

                            // Mettre à jour en base
                            $stmt = $this->db->prepare(
                                "UPDATE stripe_accounts
                                 SET stripe_location_id = :location_id, updated_at = NOW()
                                 WHERE fk_entite = :fk_entite"
                            );
                            $stmt->execute([
                                'location_id' => $locationId,
                                'fk_entite' => $entiteId
                            ]);

                            LogService::log('Location créée pour compte existant', [
                                'entite_id' => $entiteId,
                                'location_id' => $locationId
                            ]);
                        } catch (Exception $e) {
                            LogService::log('Erreur création Location pour compte existant', [
                                'level' => 'warning',
                                'entite_id' => $entiteId,
                                'error' => $e->getMessage()
                            ]);
                        }
                    }

                    return [
                        'success' => true,
                        'account_id' => $existingAccount['stripe_account_id'],
                        'location_id' => $locationId,
                        'message' => 'Compte Stripe existant',
                        'existing' => true,
                        'charges_enabled' => $stripeAccount->charges_enabled,
                        'payouts_enabled' => $stripeAccount->payouts_enabled,
                        'details_submitted' => $stripeAccount->details_submitted
                    ];
                } catch (Exception $e) {
                    // Si on ne peut pas récupérer le compte, le considérer comme invalide
                    // et permettre d'en créer un nouveau
                    $stmt = $this->db->prepare(
                        "DELETE FROM stripe_accounts WHERE fk_entite = :fk_entite"
                    );
                    $stmt->execute(['fk_entite' => $entiteId]);
                }
            }
            
            // Déchiffrer les données
            $nom = !empty($entite['encrypted_name']) ? ApiService::decryptData($entite['encrypted_name']) : '';
            $email = !empty($entite['encrypted_email']) ? ApiService::decryptSearchableData($entite['encrypted_email']) : null;
            
            // Créer le compte Stripe Connect Express
            $accountData = [
                'type' => 'express',
                'country' => 'FR',
                'capabilities' => [
                    'card_payments' => ['requested' => true],
                    'transfers' => ['requested' => true],
                ],
                'business_type' => 'non_profit', // Association
                'settings' => [
                    'payouts' => [
                        'schedule' => [
                            'interval' => 'manual' // Virements manuels pour les associations
                        ]
                    ]
                ],
                'business_profile' => [
                    'name' => $nom,
                    'product_description' => 'Vente de calendriers des pompiers',
                ],
                'metadata' => [
                    'entite_id' => $entiteId,
                    'entite_name' => $nom
                ]
            ];
            
            // Ajouter l'email seulement s'il est valide
            if ($email) {
                $accountData['email'] = $email;
                $accountData['business_profile']['support_email'] = $email;
            }
            
            // Ajouter l'URL du site web si disponible
            if (!empty($entite['site_web'])) {
                $accountData['business_profile']['url'] = $entite['site_web'];
            }
            
            $account = $this->stripe->accounts->create($accountData);

            // Créer automatiquement la Location Terminal pour Tap to Pay
            $location = null;
            $locationId = null;
            try {
                // Construire l'adresse complète
                $adresse1 = !empty($entite['adresse1']) ? $entite['adresse1'] : 'Adresse non renseignée';
                $adresse2 = !empty($entite['adresse2']) ? $entite['adresse2'] : '';
                $ville = !empty($entite['ville']) ? $entite['ville'] : 'Ville';
                $codePostal = !empty($entite['code_postal']) ? $entite['code_postal'] : '00000';

                $location = $this->stripe->terminal->locations->create([
                    'display_name' => $nom,
                    'address' => [
                        'line1' => $adresse1,
                        'line2' => $adresse2,
                        'city' => $ville,
                        'postal_code' => $codePostal,
                        'country' => 'FR',
                    ],
                    'metadata' => [
                        'entite_id' => $entiteId,
                        'type' => 'tap_to_pay'
                    ]
                ], [
                    'stripe_account' => $account->id
                ]);

                $locationId = $location->id;
                LogService::log('Location Stripe créée automatiquement', [
                    'entite_id' => $entiteId,
                    'location_id' => $locationId
                ]);
            } catch (Exception $e) {
                // Si la création de la Location échoue, logger mais continuer
                LogService::log('Erreur création Location', [
                    'level' => 'warning',
                    'entite_id' => $entiteId,
                    'error' => $e->getMessage()
                ]);
            }

            // Sauvegarder en base de données avec le location_id
            $stmt = $this->db->prepare(
                "INSERT INTO stripe_accounts (fk_entite, stripe_account_id, stripe_location_id, created_at)
                 VALUES (:fk_entite, :stripe_account_id, :stripe_location_id, NOW())"
            );
            $stmt->execute([
                'fk_entite' => $entiteId,
                'stripe_account_id' => $account->id,
                'stripe_location_id' => $locationId
            ]);

            return [
                'success' => true,
                'account_id' => $account->id,
                'location_id' => $locationId,
                'message' => 'Compte Stripe créé avec succès' . ($locationId ? ' (Location Terminal créée)' : '')
            ];
            
        } catch (ApiErrorException $e) {
            return [
                'success' => false,
                'message' => 'Erreur Stripe: ' . $e->getMessage()
            ];
        } catch (Exception $e) {
            return [
                'success' => false,
                'message' => 'Erreur: ' . $e->getMessage()
            ];
        }
    }
    
    /**
     * Récupérer les informations d'un compte Stripe Connect
     */
    public function retrieveAccount(string $accountId) {
        try {
            return $this->stripe->accounts->retrieve($accountId);
        } catch (Exception $e) {
            // Logger::getInstance()->error('Erreur récupération compte Stripe', [
            //     'account_id' => $accountId,
            //     'error' => $e->getMessage()
            // ]);
            return null;
        }
    }
    
    /**
     * Générer un lien d'onboarding pour finaliser la configuration du compte
     */
    public function createOnboardingLink(string $accountId, string $returnUrl, string $refreshUrl): array {
        try {
            LogService::log('StripeService::createOnboardingLink début', [
                'account_id' => $accountId,
                'return_url' => $returnUrl,
                'refresh_url' => $refreshUrl
            ]);

            $accountLink = $this->stripe->accountLinks->create([
                'account' => $accountId,
                'refresh_url' => $refreshUrl,
                'return_url' => $returnUrl,
                'type' => 'account_onboarding',
            ]);

            LogService::log('StripeService::createOnboardingLink succès', [
                'url' => $accountLink->url
            ]);

            return [
                'success' => true,
                'url' => $accountLink->url
            ];

        } catch (ApiErrorException $e) {
            LogService::log('StripeService::createOnboardingLink erreur Stripe', [
                'level' => 'error',
                'error' => $e->getMessage(),
                'code' => $e->getCode()
            ]);

            return [
                'success' => false,
                'message' => 'Erreur Stripe: ' . $e->getMessage()
            ];
        } catch (\Exception $e) {
            LogService::log('StripeService::createOnboardingLink erreur générale', [
                'level' => 'error',
                'error' => $e->getMessage()
            ]);
            
            return [
                'success' => false,
                'message' => 'Erreur: ' . $e->getMessage()
            ];
        }
    }
    
    /**
     * Créer une Location pour Terminal/Tap to Pay
     */
    public function createLocation(int $entiteId): array {
        try {
            // Récupérer le compte Stripe et l'entité
            $stmt = $this->db->prepare(
                "SELECT sa.*, e.* 
                 FROM stripe_accounts sa
                 JOIN entites e ON sa.fk_entite = e.id
                 WHERE sa.fk_entite = :fk_entite"
            );
            $stmt->execute(['fk_entite' => $entiteId]);
            $data = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$data) {
                throw new Exception("Compte Stripe non trouvé pour cette entité");
            }
            
            // Déchiffrer les données de l'entité
            $nom = !empty($data['encrypted_name']) ? ApiService::decryptData($data['encrypted_name']) : 'Amicale';
            
            // Construire l'adresse complète
            $adresse1 = !empty($data['adresse1']) ? $data['adresse1'] : '';
            $adresse2 = !empty($data['adresse2']) ? $data['adresse2'] : '';
            $adresse = trim($adresse1 . ' ' . $adresse2);
            if (empty($adresse)) {
                $adresse = 'Adresse non renseignée';
            }
            
            $ville = !empty($data['ville']) ? $data['ville'] : 'Ville';
            $codePostal = !empty($data['code_postal']) ? $data['code_postal'] : '00000';
            
            // Créer la location
            $location = $this->stripe->terminal->locations->create([
                'display_name' => $nom,
                'address' => [
                    'line1' => $adresse,
                    'city' => $ville,
                    'postal_code' => $codePostal,
                    'country' => 'FR',
                ],
                'metadata' => [
                    'entite_id' => $entiteId,
                    'type' => 'tap_to_pay'
                ]
            ], [
                'stripe_account' => $data['stripe_account_id']
            ]);
            
            // Mettre à jour en base
            $stmt = $this->db->prepare(
                "UPDATE stripe_accounts 
                 SET stripe_location_id = :location_id, updated_at = NOW()
                 WHERE fk_entite = :fk_entite"
            );
            $stmt->execute([
                'location_id' => $location->id,
                'fk_entite' => $entiteId
            ]);
            
            return [
                'success' => true,
                'location_id' => $location->id,
                'message' => 'Location créée avec succès'
            ];
            
        } catch (Exception $e) {
            return [
                'success' => false,
                'message' => 'Erreur: ' . $e->getMessage()
            ];
        }
    }
    
    /**
     * Créer un Connection Token pour Terminal/Tap to Pay
     */
    public function createConnectionToken(int $entiteId): array {
        try {
            // Récupérer le compte et la location
            $stmt = $this->db->prepare(
                "SELECT * FROM stripe_accounts WHERE fk_entite = :fk_entite"
            );
            $stmt->execute(['fk_entite' => $entiteId]);
            $account = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$account || !$account['stripe_location_id']) {
                throw new Exception("Location Stripe non configurée pour cette entité");
            }
            
            // Créer le token
            $connectionToken = $this->stripe->terminal->connectionTokens->create([
                'location' => $account['stripe_location_id']
            ], [
                'stripe_account' => $account['stripe_account_id']
            ]);
            
            return [
                'success' => true,
                'secret' => $connectionToken->secret
            ];
            
        } catch (Exception $e) {
            return [
                'success' => false,
                'message' => 'Erreur: ' . $e->getMessage()
            ];
        }
    }
    
    /**
     * Créer une intention de paiement
     */
    public function createPaymentIntent(array $params): array {
        try {
            $amount = $params['amount'] ?? 0;
            $entiteId = $params['fk_entite'] ?? 0;
            $userId = $params['fk_user'] ?? 0;
            $metadata = $params['metadata'] ?? [];
            $paymentMethodTypes = $params['payment_method_types'] ?? ['card_present'];

            if ($amount < 100) {
                throw new Exception("Le montant minimum est de 1€");
            }

            // Récupérer le compte Stripe
            $stmt = $this->db->prepare(
                "SELECT * FROM stripe_accounts WHERE fk_entite = :fk_entite"
            );
            $stmt->execute(['fk_entite' => $entiteId]);
            $account = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$account) {
                throw new Exception("Compte Stripe non trouvé");
            }

            // Déterminer le mode : Tap to Pay (card_present) ou Payment Link (card)
            $isTapToPay = in_array('card_present', $paymentMethodTypes);

            // Configuration du PaymentIntent selon le mode
            $paymentIntentData = [
                'amount' => $amount,
                'currency' => 'eur',
                'payment_method_types' => $paymentMethodTypes,
                'capture_method' => 'automatic',
                'metadata' => array_merge($metadata, [
                    'entite_id' => $entiteId,
                    'user_id' => $userId,
                    'calendrier_annee' => date('Y'),
                ]),
            ];

            // Options Stripe (avec ou sans stripe_account)
            $stripeOptions = [];

            if ($isTapToPay) {
                // TAP TO PAY : Paiement direct sur le compte connecté
                // Le PaymentIntent est créé sur le compte de l'amicale
                $stripeOptions['stripe_account'] = $account['stripe_account_id'];
            } else {
                // PAYMENT LINK / WEB : Paiement via la plateforme avec transfert
                // Le PaymentIntent est créé sur la plateforme et transféré
                $paymentIntentData['transfer_data'] = [
                    'destination' => $account['stripe_account_id'],
                ];
            }

            // Créer le PaymentIntent
            $paymentIntent = $this->stripe->paymentIntents->create(
                $paymentIntentData,
                $stripeOptions
            );

            // Note : Le payment_intent_id est sauvegardé dans ope_pass.stripe_payment_id par le controller

            return [
                'success' => true,
                'client_secret' => $paymentIntent->client_secret,
                'payment_intent_id' => $paymentIntent->id,
                'amount' => $amount,
                'mode' => $isTapToPay ? 'tap_to_pay' : 'payment_link'
            ];
            
        } catch (Exception $e) {
            return [
                'success' => false,
                'message' => 'Erreur: ' . $e->getMessage()
            ];
        }
    }

    /**
     * Créer un Payment Link Stripe pour paiement par QR Code
     *
     * @param array $params [
     *   'amount' => int (en centimes),
     *   'currency' => string (défaut: 'eur'),
     *   'description' => string,
     *   'passage_id' => int,
     *   'metadata' => array
     * ]
     * @return array ['success' => bool, 'payment_link_id' => string, 'url' => string, 'amount' => int]
     */
    public function createPaymentLink(array $params): array {
        try {
            $amount = $params['amount'] ?? 0;
            $passageId = $params['passage_id'] ?? 0;

            if ($amount <= 0) {
                throw new Exception("Le montant doit être supérieur à 0");
            }

            // Récupérer les infos du passage avec opération et entité
            $stmt = $this->db->prepare("
                SELECT p.*, o.fk_entite, o.id as operation_id, sa.stripe_account_id
                FROM ope_pass p
                JOIN operations o ON p.fk_operation = o.id
                JOIN stripe_accounts sa ON o.fk_entite = sa.fk_entite
                WHERE p.id = :passage_id
            ");
            $stmt->execute(['passage_id' => $passageId]);
            $passage = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$passage) {
                throw new Exception("Passage non trouvé");
            }

            if (!$passage['stripe_account_id']) {
                throw new Exception("Stripe non activé pour cette amicale");
            }

            // Préparer les metadata
            $metadata = array_merge($params['metadata'] ?? [], [
                'passage_id' => (string)$passageId,
                'operation_id' => (string)$passage['operation_id'],
                'amicale_id' => (string)$passage['fk_entite'],
                'fk_user' => (string)$passage['fk_user'], // ID du membre (ope_users.id)
                'created_at' => (string)time(), // Timestamp Unix de création du Payment Link
                'type' => 'qr_code_payment'
            ]);

            // Créer le Payment Link sur le compte Connect
            $paymentLink = $this->stripe->paymentLinks->create([
                'line_items' => [
                    [
                        'price_data' => [
                            'currency' => $params['currency'] ?? 'eur',
                            'product_data' => [
                                'name' => $params['description'] ?? 'Calendrier pompiers',
                            ],
                            'unit_amount' => $amount,
                        ],
                        'quantity' => 1,
                    ],
                ],
                'metadata' => $metadata,
                'after_completion' => [
                    'type' => 'hosted_confirmation',
                    'hosted_confirmation' => [
                        'custom_message' => 'Merci pour votre paiement ! Votre reçu vous sera envoyé par email.',
                    ],
                ],
                'payment_method_types' => ['card'],
                'billing_address_collection' => 'auto',
            ], [
                'stripe_account' => $passage['stripe_account_id']
            ]);

            // Logger la création
            LogService::log('Payment Link créé', [
                'payment_link_id' => $paymentLink->id,
                'passage_id' => $passageId,
                'amount' => $amount,
                'amicale_id' => $passage['fk_entite']
            ]);

            // Mettre à jour le passage avec le payment_link_id
            $stmt = $this->db->prepare("
                UPDATE ope_pass
                SET stripe_payment_link_id = :link_id, updated_at = NOW()
                WHERE id = :passage_id
            ");
            $stmt->execute([
                'link_id' => $paymentLink->id,
                'passage_id' => $passageId
            ]);

            return [
                'success' => true,
                'payment_link_id' => $paymentLink->id,
                'url' => $paymentLink->url,
                'amount' => $amount
            ];

        } catch (ApiErrorException $e) {
            LogService::log('Erreur Stripe Payment Link', [
                'level' => 'error',
                'error' => $e->getMessage(),
                'passage_id' => $passageId ?? null
            ]);

            return [
                'success' => false,
                'message' => 'Erreur Stripe: ' . $e->getMessage()
            ];
        } catch (Exception $e) {
            return [
                'success' => false,
                'message' => 'Erreur: ' . $e->getMessage()
            ];
        }
    }

    /**
     * Récupérer le statut d'un PaymentIntent depuis Stripe
     */
    public function getPaymentIntentStatus(string $paymentIntentId): array {
        try {
            $paymentIntent = $this->stripe->paymentIntents->retrieve($paymentIntentId);

            return [
                'success' => true,
                'status' => $paymentIntent->status,
                'amount' => $paymentIntent->amount,
                'currency' => $paymentIntent->currency,
                'payment_method' => $paymentIntent->payment_method,
                'created' => $paymentIntent->created
            ];
        } catch (Exception $e) {
            return [
                'success' => false,
                'message' => 'Erreur: ' . $e->getMessage()
            ];
        }
    }

    /**
     * Annuler un PaymentIntent Stripe
     *
     * @param string $paymentIntentId L'ID du PaymentIntent à annuler
     * @return array ['success' => bool, 'status' => string|null, 'message' => string|null]
     */
    public function cancelPaymentIntent(string $paymentIntentId): array {
        try {
            // Annuler le PaymentIntent via l'API Stripe
            $paymentIntent = $this->stripe->paymentIntents->cancel($paymentIntentId);

            LogService::log('PaymentIntent annulé', [
                'payment_intent_id' => $paymentIntentId,
                'status' => $paymentIntent->status
            ]);

            return [
                'success' => true,
                'status' => $paymentIntent->status,
                'payment_intent_id' => $paymentIntentId
            ];

        } catch (ApiErrorException $e) {
            LogService::log('Erreur annulation PaymentIntent Stripe', [
                'level' => 'error',
                'payment_intent_id' => $paymentIntentId,
                'error' => $e->getMessage()
            ]);

            return [
                'success' => false,
                'message' => 'Erreur Stripe: ' . $e->getMessage()
            ];
        } catch (Exception $e) {
            return [
                'success' => false,
                'message' => 'Erreur: ' . $e->getMessage()
            ];
        }
    }

    /**
     * Obtenir le mode actuel (test ou live)
     */
    public function isTestMode(): bool {
        return $this->testMode;
    }

    /**
     * Obtenir la clé publique pour le frontend
     */
    public function getPublicKey(): string {
        $stripeConfig = $this->config->getStripeConfig();
        return $this->testMode
            ? $stripeConfig['public_key_test']
            : $stripeConfig['public_key_live'];
    }
}