<?php

declare(strict_types=1);

namespace App\Services;

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

class StravaService
{
    private Client $http;
    private string $clientId;
    private string $clientSecret;
    private string $redirectUri;
    private string $encryptionKey;
    private int $failureCount = 0;
    private const MAX_FAILURES = 5;

    public function __construct()
    {
        $this->clientId     = $_ENV['STRAVA_CLIENT_ID'] ?? '';
        $this->clientSecret = $_ENV['STRAVA_CLIENT_SECRET'] ?? '';
        $this->redirectUri  = $_ENV['STRAVA_REDIRECT_URI'] ?? '';
        $this->encryptionKey = $_ENV['ENCRYPTION_KEY'] ?? '';
        $this->http         = new Client(['timeout' => 30]);
    }

    // ─── Chiffrement AES-256-CBC ───

    private function getEncryptionKey(): string
    {
        $raw = $this->encryptionKey;
        if (!$raw || strlen($raw) < 64) {
            // Clé par défaut dérivée du nom de domaine — à remplacer par ENCRYPTION_KEY dans .env
            error_log('[StravaService] ENCRYPTION_KEY manquante ou trop courte — utilisation de la clé de fallback');
            $raw = hash('sha256', ($_ENV['APP_NAME'] ?? 'mykollector') . ($_ENV['DB_DATABASE'] ?? 'mykollufab'), false);
        }
        return hex2bin(substr($raw, 0, 64)); // 32 bytes
    }

    public function encrypt(string $value): string
    {
        $key    = $this->getEncryptionKey();
        $iv     = openssl_random_pseudo_bytes(16);
        $cipher = openssl_encrypt($value, 'AES-256-CBC', $key, 0, $iv);
        return base64_encode($iv . $cipher);
    }

    public function decrypt(string $value): string
    {
        try {
            $key  = $this->getEncryptionKey();
            $data = base64_decode($value);
            $iv   = substr($data, 0, 16);
            $cipher = substr($data, 16);
            $decrypted = openssl_decrypt($cipher, 'AES-256-CBC', $key, 0, $iv);
            if ($decrypted === false) {
                throw new \Exception('Déchiffrement échoué');
            }
            return $decrypted;
        } catch (\Exception $e) {
            DB::table('logs_audit')->insert([
                'action'    => 'strava_token_decrypt_error',
                'details'   => json_encode(['error' => $e->getMessage()]),
                'created_at'=> date('Y-m-d H:i:s'),
            ]);
            throw $e;
        }
    }

    // ─── OAuth2 ───

    public function getAuthUrl(int $userId): string
    {
        $params = http_build_query([
            'client_id'     => $this->clientId,
            'redirect_uri'  => $this->redirectUri,
            'response_type' => 'code',
            'approval_prompt' => 'auto',
            'scope'         => 'activity:read',
            'state'         => $userId,
        ]);
        return 'https://www.strava.com/oauth/authorize?' . $params;
    }

    public function exchangeCode(string $code, int $userId): bool
    {
        try {
            $response = $this->http->post('https://www.strava.com/oauth/token', [
                'form_params' => [
                    'client_id'     => $this->clientId,
                    'client_secret' => $this->clientSecret,
                    'code'          => $code,
                    'grant_type'    => 'authorization_code',
                ]
            ]);

            $data = json_decode((string)$response->getBody(), true);

            DB::table('users')->where('id', $userId)->update([
                'strava_connected'    => 1,
                'strava_id'           => $data['athlete']['id'] ?? null,
                'strava_access_token' => $this->encrypt($data['access_token'] ?? ''),
                'strava_refresh_token'=> $this->encrypt($data['refresh_token'] ?? ''),
                'strava_token_expires'=> $data['expires_at'] ?? 0,
            ]);

            return true;
        } catch (RequestException $e) {
            error_log('[Strava] exchangeCode: ' . $e->getMessage());
            return false;
        }
    }

    public function refreshToken(int $userId): bool
    {
        $user = DB::table('users')->where('id', $userId)->first();
        if (!$user || !$user->strava_refresh_token) return false;

        try {
            $refreshToken = $this->decrypt($user->strava_refresh_token);

            $response = $this->http->post('https://www.strava.com/oauth/token', [
                'form_params' => [
                    'client_id'     => $this->clientId,
                    'client_secret' => $this->clientSecret,
                    'grant_type'    => 'refresh_token',
                    'refresh_token' => $refreshToken,
                ]
            ]);

            $data = json_decode((string)$response->getBody(), true);

            DB::table('users')->where('id', $userId)->update([
                'strava_access_token' => $this->encrypt($data['access_token'] ?? ''),
                'strava_refresh_token'=> $this->encrypt($data['refresh_token'] ?? ''),
                'strava_token_expires'=> $data['expires_at'] ?? 0,
            ]);

            return true;
        } catch (\Exception $e) {
            error_log('[Strava] refreshToken user ' . $userId . ': ' . $e->getMessage());
            return false;
        }
    }

    private function getAccessToken(int $userId): ?string
    {
        $user = DB::table('users')->where('id', $userId)->first();
        if (!$user || !$user->strava_access_token) return null;

        try {
            // Rafraîchir si expiré
            if ($user->strava_token_expires && $user->strava_token_expires < time() + 60) {
                $this->refreshToken($userId);
                $user = DB::table('users')->where('id', $userId)->first();
            }

            return $this->decrypt($user->strava_access_token);
        } catch (\Exception $e) {
            return null;
        }
    }

    // ─── API Strava ───

    public function getActivites(int $userId, int $page = 1, int $limit = 10): array
    {
        $token = $this->getAccessToken($userId);
        if (!$token) return [];

        try {
            $response = $this->http->get('https://www.strava.com/api/v3/athlete/activities', [
                'headers' => ['Authorization' => 'Bearer ' . $token],
                'query'   => ['page' => $page, 'per_page' => $limit],
            ]);

            $activities = json_decode((string)$response->getBody(), true) ?? [];

            return array_map(function ($act) use ($userId) {
                $parcours = $this->detecterParcours($act);
                $alreadyImported = DB::table('chronos')
                    ->where('strava_activity_id', (string)$act['id'])
                    ->exists();

                return [
                    'strava_id'        => (string)$act['id'],
                    'nom'              => $act['name'] ?? '',
                    'date'             => $act['start_date'] ?? '',
                    'duree_secondes'   => $act['elapsed_time'] ?? 0,
                    'parcours_detecte' => $parcours,
                    'deja_importe'     => $alreadyImported,
                ];
            }, $activities);
        } catch (RequestException $e) {
            $this->failureCount++;
            error_log('[Strava] getActivites: ' . $e->getMessage());
            return [];
        }
    }

    public function getActivite(int $userId, string $activiteId): ?array
    {
        $token = $this->getAccessToken($userId);
        if (!$token) return null;

        try {
            $response = $this->http->get("https://www.strava.com/api/v3/activities/{$activiteId}", [
                'headers' => ['Authorization' => 'Bearer ' . $token],
            ]);
            return json_decode((string)$response->getBody(), true);
        } catch (RequestException $e) {
            return null;
        }
    }

    // ─── Déduplication v1.1 ───

    public function detecterParcours(array $stravaActivity): ?array
    {
        $parcours = DB::table('produits')->where('actif', 1)->get();
        $best     = null;
        $bestScore = 0;

        foreach ($parcours as $p) {
            $score = $this->calculerScore($stravaActivity, $p);
            if ($score > $bestScore) {
                $bestScore = $score;
                $best = $p;
            }
        }

        if (!$best || $bestScore < 0.50) return null;

        return [
            'slug'  => $best->slug,
            'nom'   => $best->nom,
            'score' => $bestScore,
        ];
    }

    private function calculerScore(array $activity, object $parcours): float
    {
        $score = 0.0;

        // Score par mots-clés dans le nom
        $keywords = json_decode($parcours->strava_keywords ?? '[]', true) ?? [];
        $activityName = strtolower($activity['name'] ?? '');
        foreach ($keywords as $kw) {
            if (str_contains($activityName, strtolower($kw))) {
                $score += 0.30;
                break;
            }
        }

        // Score par proximité GPS (start_latlng)
        $startLat = $activity['start_latlng'][0] ?? null;
        $startLng = $activity['start_latlng'][1] ?? null;

        if ($startLat && $startLng && $parcours->depart_lat && $parcours->depart_lng) {
            $dist = $this->haversine(
                (float)$startLat, (float)$startLng,
                (float)$parcours->depart_lat, (float)$parcours->depart_lng
            );
            if ($dist < 2) $score += 0.50; // < 2km
            elseif ($dist < 5) $score += 0.30;
            elseif ($dist < 10) $score += 0.10;
        }

        // Score par durée similaire (rough check)
        $elapsed = (int)($activity['elapsed_time'] ?? 0);
        if ($elapsed > 3600 && $elapsed < 86400) $score += 0.20;

        return min(1.0, $score);
    }

    private function haversine(float $lat1, float $lng1, float $lat2, float $lng2): float
    {
        $R = 6371;
        $dLat = deg2rad($lat2 - $lat1);
        $dLon = deg2rad($lng2 - $lng1);
        $a = sin($dLat/2)**2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon/2)**2;
        return $R * 2 * atan2(sqrt($a), sqrt(1-$a));
    }

    public function importerActivite(int $userId, string $activiteId): array
    {
        // Vérifier déjà importé
        if (DB::table('chronos')->where('strava_activity_id', $activiteId)->exists()) {
            return ['status' => 'already_imported'];
        }

        $activity = $this->getActivite($userId, $activiteId);
        if (!$activity) {
            throw new \Exception('Activité Strava introuvable');
        }

        $parcours = $this->detecterParcours($activity);
        if (!$parcours) {
            throw new \Exception('Aucun parcours MKO détecté dans cette activité');
        }

        $produit = DB::table('produits')->where('slug', $parcours['slug'])->first();
        if (!$produit) {
            throw new \Exception('Parcours MKO introuvable en base');
        }

        // Déduplication : chercher chrono QR dans la fenêtre
        $config = DB::table('segment_tracking_config')->where('produit_id', $produit->id)->first();
        $windowMinutes = $config->strava_dedup_window_minutes ?? 120;

        $activityDate = date('Y-m-d H:i:s', strtotime($activity['start_date'] ?? 'now'));
        $windowStart  = date('Y-m-d H:i:s', strtotime($activityDate) - $windowMinutes * 60);
        $windowEnd    = date('Y-m-d H:i:s', strtotime($activityDate) + $windowMinutes * 60);

        $existingChrono = DB::table('chronos')
            ->where('user_id', $userId)
            ->where('produit_id', $produit->id)
            ->where('source', 'qr')
            ->whereBetween('date_debut', [$windowStart, $windowEnd])
            ->orderBy('date_debut', 'desc')
            ->first();

        if ($existingChrono) {
            // Enrichir le chrono QR existant
            DB::table('chronos')->where('id', $existingChrono->id)->update([
                'strava_activity_id' => $activiteId,
            ]);
            return ['status' => 'linked', 'chrono_id' => $existingChrono->id, 'message' => 'Activité Strava liée à votre chrono existant'];
        }

        // Créer nouveau chrono
        $duree = (int)($activity['elapsed_time'] ?? 0);
        $chronoId = DB::table('chronos')->insertGetId([
            'user_id'           => $userId,
            'produit_id'        => $produit->id,
            'duree_secondes'    => $duree,
            'source'            => 'strava',
            'statut'            => 'valide',
            'date_debut'        => $activityDate,
            'date_fin'          => date('Y-m-d H:i:s', strtotime($activityDate) + $duree),
            'strava_activity_id'=> $activiteId,
            'score_detection'   => $parcours['score'],
            'auto_valide'       => 1,
            'created_at'        => date('Y-m-d H:i:s'),
        ]);

        // Mettre à jour classement
        $this->updateClassement($userId, $produit->id, $chronoId, $duree, $activityDate, 'strava');

        return ['status' => 'created', 'chrono_id' => $chronoId];
    }

    public function updateClassement(int $userId, int $produitId, int $chronoId, int $duree, string $date, string $source): void
    {
        $existing = DB::table('classements')
            ->where('user_id', $userId)
            ->where('produit_id', $produitId)
            ->first();

        if (!$existing || $duree < $existing->duree_secondes) {
            DB::table('classements')->updateOrInsert(
                ['user_id' => $userId, 'produit_id' => $produitId],
                [
                    'chrono_id'       => $chronoId,
                    'duree_secondes'  => $duree,
                    'date_realisation'=> $date,
                    'source'          => $source,
                    'created_at'      => date('Y-m-d H:i:s'),
                ]
            );
        }
    }

    // ─── Webhook ───

    public function verifierWebhook(array $params): ?string
    {
        $config = DB::table('strava_config')->first();
        if (!$config) return null;

        if (($params['hub.verify_token'] ?? '') === $config->verify_token) {
            return $params['hub.challenge'] ?? null;
        }
        return null;
    }

    public function traiterWebhook(array $body): void
    {
        // Vérification sécurité
        $config = DB::table('strava_config')->first();
        if (!$config) return;

        $subscriptionId = $body['subscription_id'] ?? null;
        if ($subscriptionId != $config->webhook_id) {
            error_log('[Strava Webhook] subscription_id invalide: ' . $subscriptionId);
            return; // 200 silencieux
        }

        if (($body['aspect_type'] ?? '') !== 'create' || ($body['object_type'] ?? '') !== 'activity') {
            return; // Ignorer les événements autres que création
        }

        $stravaUserId = $body['owner_id'] ?? null;
        $activiteId   = (string)($body['object_id'] ?? '');

        // Trouver l'utilisateur MKO
        $user = DB::table('users')->where('strava_id', $stravaUserId)->first();
        if (!$user) {
            error_log('[Strava Webhook] owner_id inconnu: ' . $stravaUserId);
            return;
        }

        // Éviter les doublons
        if (DB::table('chronos')->where('strava_activity_id', $activiteId)->exists()) {
            return;
        }

        try {
            $result = $this->importerActivite($user->id, $activiteId);
            DB::table('logs_audit')->insert([
                'user_id'   => $user->id,
                'action'    => 'strava_webhook_traite',
                'entite'    => 'chrono',
                'entite_id' => $result['chrono_id'] ?? null,
                'details'   => json_encode(['strava_activity_id' => $activiteId, 'status' => $result['status']]),
                'created_at'=> date('Y-m-d H:i:s'),
            ]);
        } catch (\Exception $e) {
            error_log('[Strava Webhook] Erreur: ' . $e->getMessage());
        }
    }

    public function getAlertes(int $userId): array
    {
        // Activités Strava avec score 0.50-0.74 non encore importées
        // On récupère les dernières activités et on filtre
        $activites = $this->getActivites($userId, 1, 30);
        $alertes   = [];

        foreach ($activites as $act) {
            if (!$act['deja_importe'] && isset($act['parcours_detecte'])) {
                $score = $act['parcours_detecte']['score'] ?? 0;
                if ($score >= 0.50 && $score < 0.75) {
                    $alertes[] = array_merge($act, [
                        'message' => sprintf('Score %.0f%% — seuil 75%% — import manuel possible', $score * 100)
                    ]);
                }
            }
        }

        return $alertes;
    }

    public function disconnect(int $userId): void
    {
        DB::table('users')->where('id', $userId)->update([
            'strava_connected'     => 0,
            'strava_id'            => null,
            'strava_access_token'  => null,
            'strava_refresh_token' => null,
            'strava_token_expires' => null,
        ]);
    }
}
