<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class JikanService
{
    const BASE = 'https://api.jikan.moe/v4';
    const CACHE_TTL = 60 * 60 * 24 * 7; // 1 week

    /**
     * Given a root MAL ID, walk the sequel chain and return
     * an ordered array of MAL IDs: [s1_mal_id, s2_mal_id, ...]
     */
    public function fetchSeasonMalIds(string $rootMalId): array
    {
        $chain  = [];
        $visited = [];
        $current = $rootMalId;

        // Walk up to 10 sequels (safety limit)
        for ($i = 0; $i < 10; $i++) {
            if (in_array($current, $visited)) break;
            $visited[] = $current;
            $chain[]   = $current;

            $sequel = $this->getSequel($current);
            if (!$sequel) break;
            $current = $sequel;

            // Jikan rate limit: max 3 req/s — small sleep between calls
            usleep(400_000); // 400ms
        }

        return $chain;
    }

    /**
     * Returns the MAL ID of the direct "Sequel" relation, or null.
     */
    public function getSequel(string $malId): ?string
    {
        $key  = "jikan_relations_{$malId}";
        $data = Cache::remember($key, self::CACHE_TTL, function () use ($malId) {
            $res = Http::timeout(10)->get(self::BASE . "/anime/{$malId}/relations");
            if (!$res->ok()) return null;
            return $res->json();
        });

        if (!$data || empty($data['data'])) return null;

        foreach ($data['data'] as $rel) {
            if (strtolower($rel['relation']) === 'sequel') {
                foreach ($rel['entry'] as $entry) {
                    if ($entry['type'] === 'anime') {
                        return (string) $entry['mal_id'];
                    }
                }
            }
        }

        return null;
    }

    /**
     * Search Jikan by title, return best-matching MAL ID or null.
     * Tries title_jp first, then title_en, then title.
     * $animeType: 'series'|'movie'|'ova'|'ona'|'special' (optional, improves accuracy)
     */
    public function searchMalId(string $title, ?string $titleEn = null, ?string $titleJp = null, ?string $animeType = null): ?string
    {
        $queries = array_values(array_filter(array_unique([$titleJp, $titleEn, $title])));
        foreach ($queries as $i => $q) {
            $malId = $this->searchByQuery($q, $animeType);
            if ($malId) return $malId;
            if ($i < count($queries) - 1) usleep(350_000);
        }
        return null;
    }

    private function searchByQuery(string $query, ?string $animeType = null): ?string
    {
        // Map our type → Jikan type; try specific first then fallback to any
        $jikanType = match ($animeType) {
            'movie'   => 'movie',
            'ova'     => 'ova',
            'ona'     => 'ona',
            'special' => 'special',
            default   => 'tv',
        };

        // Try with specific type, then without type restriction (catches edge cases)
        $typesToTry = array_unique([$jikanType, null]);

        foreach ($typesToTry as $type) {
            $cacheKey = 'jikan_s2_' . md5($query . '_' . ($type ?? 'any'));
            $data = Cache::remember($cacheKey, self::CACHE_TTL, function () use ($query, $type) {
                $params = ['q' => $query, 'limit' => 8, 'sfw' => false];
                if ($type) $params['type'] = $type;
                $res = Http::timeout(10)->get(self::BASE . '/anime', $params);
                if (!$res->ok()) return null;
                return $res->json('data');
            });

            if (!empty($data)) {
                $best = $this->bestMatch($query, $data);
                if ($best) return (string) $best['mal_id'];
            }

            if ($type !== null) usleep(300_000); // rate limit between type fallback
        }

        return null;
    }

    /**
     * Pick the result whose title best matches the query via similar_text.
     * Falls back to first result if nothing scores > 40%.
     */
    private function bestMatch(string $query, array $results): ?array
    {
        $q = mb_strtolower(trim($query));
        $best      = null;
        $bestScore = 0;

        foreach ($results as $item) {
            $candidates = array_filter([
                $item['title']          ?? null,
                $item['title_english']  ?? null,
                $item['title_japanese'] ?? null,
            ]);
            foreach ($candidates as $t) {
                similar_text($q, mb_strtolower(trim($t)), $pct);
                if ($pct > $bestScore) {
                    $bestScore = $pct;
                    $best      = $item;
                }
            }
        }

        // If best match is decent or we have no choice, return it
        return ($best && $bestScore >= 35) ? $best : ($results[0] ?? null);
    }

    /**
     * Fetch anime details (title_english, title, episodes count etc.)
     */
    public function getAnimeDetails(string $malId): ?array
    {
        $key  = "jikan_anime_{$malId}";
        $data = Cache::remember($key, self::CACHE_TTL, function () use ($malId) {
            $res = Http::timeout(10)->get(self::BASE . "/anime/{$malId}");
            if (!$res->ok()) return null;
            return $res->json('data');
        });

        return $data;
    }
}
