package de.oaa.libredeck.web.service.spotify;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import de.oaa.libredeck.web.model.PlaylistSummary;
import de.oaa.libredeck.web.model.Track;
import de.oaa.libredeck.web.service.TrackNormalizer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
public class SpotifyApiClient {

    private static final String API_BASE   = "https://api.spotify.com/v1";
    private static final long   RATE_LIMIT = 100; // ms

    private static final ThreadLocal<String> TOKEN_OVERRIDE = new ThreadLocal<>();

    private final HttpClient        http       = HttpClient.newHttpClient();
    private final ObjectMapper      mapper     = new ObjectMapper();
    private final SpotifyTokenStore tokenStore;

    private long lastRequest = 0;

    /** Call on the request thread before submitting an async task that will use this client. */
    public void pinTokenForThread(String token) { TOKEN_OVERRIDE.set(token); }
    public void clearThreadToken()              { TOKEN_OVERRIDE.remove(); }

    public String fetchPlaylistTitle(String playlistId) throws IOException {
        JsonNode root = mapper.readTree(get(API_BASE + "/playlists/" + playlistId + "?fields=name"));
        return root.path("name").asText("Playlist " + playlistId);
    }

    public List<Track> fetchTracks(String playlistId) throws IOException {
        List<Track> result = new ArrayList<>();
        String rawResponse = get(API_BASE + "/playlists/" + playlistId);
        log.info("Spotify playlist response: {}", rawResponse);
        JsonNode playlist   = mapper.readTree(rawResponse);
        JsonNode tracksPage = playlist.path("items");
        collectItems(tracksPage.path("items"), result);
        String url = tracksPage.path("next").isNull() || tracksPage.path("next").isMissingNode()
                ? null : tracksPage.path("next").asText();
        while (url != null) {
            try {
                JsonNode page = mapper.readTree(get(url));
                collectItems(page.path("items"), result);
                JsonNode next = page.path("next");
                url = next.isMissingNode() || next.isNull() ? null : next.asText();
            } catch (IOException e) {
                log.warn("Spotify pagination aborted ({}), returning {} tracks", e.getMessage(), result.size());
                break;
            }
        }
        return result;
    }

    public List<PlaylistSummary> fetchUserPlaylists() throws IOException {
        List<PlaylistSummary> result = new ArrayList<>();
        String url = API_BASE + "/me/playlists?limit=50";
        while (url != null) {
                JsonNode root  = mapper.readTree(get(url));
            JsonNode items = root.path("items");
            for (JsonNode item : items) {
                String id = item.path("id").asText("");
                if (id.isEmpty()) continue;
                String name  = item.path("name").asText("");
                int    total = item.path("tracks").path("total").asInt(0);
                String cover = item.path("images").isArray() && !item.path("images").isEmpty()
                        ? item.path("images").get(0).path("url").asText("") : "";
                result.add(new PlaylistSummary(id, name, total, cover));
            }
            JsonNode next = root.path("next");
            url = next.isMissingNode() || next.isNull() ? null : next.asText();
        }
        return result;
    }

    public String fetchTrackYear(String trackId) {
        try {
            JsonNode track = mapper.readTree(get(API_BASE + "/tracks/" + trackId));
            String   date  = track.path("album").path("release_date").asText("");
            return date.length() >= 4 ? date.substring(0, 4) : "";
        } catch (IOException e) {
            log.warn("Could not fetch Spotify track year for {}: {}", trackId, e.getMessage());
            return "";
        }
    }

    // ── HTTP helpers ──────────────────────────────────────────────────────────

    private synchronized String get(String url) throws IOException {
        String token = TOKEN_OVERRIDE.get();
        if (token == null) {
            if (!tokenStore.hasValidToken()) throw new SpotifyAuthRequiredException();
            token = tokenStore.getAccessToken();
        }
        rateLimitWait();

        HttpResponse<String> response = send(url, token);
        if (response.statusCode() != 200) {
            log.warn("Spotify API {} → {}: {}", url, response.statusCode(), response.body());
            throw new IOException("Spotify API error " + response.statusCode() + ": " + response.body());
        }
        return response.body();
    }

    private HttpResponse<String> send(String url, String token) throws IOException {
        try {
            return http.send(
                    HttpRequest.newBuilder()
                            .uri(URI.create(url))
                            .header("Authorization", "Bearer " + token)
                            .GET().build(),
                    HttpResponse.BodyHandlers.ofString());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IOException("Request interrupted", e);
        }
    }

    private void collectItems(JsonNode items, List<Track> result) {
        if (!items.isArray()) return;
        for (JsonNode item : items) {
            JsonNode track = item.path("item");
            if (track.isMissingNode() || track.isNull()) track = item.path("track");
            if (track.isMissingNode() || track.isNull()) continue;
            String id = track.path("id").asText("");
            if (id.isEmpty()) continue;

            String   rawTitle  = track.path("name").asText();
            String   rawArtist = "";
            JsonNode artists   = track.path("artists");
            if (artists.isArray() && !artists.isEmpty()) {
                rawArtist = artists.get(0).path("name").asText("");
            }
            String streamUrl = track.path("external_urls").path("spotify")
                    .asText("https://open.spotify.com/track/" + id);

            String date = track.path("album").path("release_date").asText("");
            String year = date.length() >= 4 ? date.substring(0, 4) : "";

            String[] ta = TrackNormalizer.extractFeaturing(TrackNormalizer.cleanTitle(rawTitle), rawArtist);
            result.add(new Track(id, ta[0], ta[1], year, streamUrl));
        }
    }

    private void rateLimitWait() {
        long wait = RATE_LIMIT - (System.currentTimeMillis() - lastRequest);
        if (wait > 0) {
            try { Thread.sleep(wait); } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        lastRequest = System.currentTimeMillis();
    }
}
