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

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 AppleMusicApiClient {

    private static final String API_BASE   = "https://api.music.apple.com/v1";
    private static final long   RATE_LIMIT = 150;

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

    private final HttpClient               http      = HttpClient.newHttpClient();
    private final ObjectMapper             mapper    = new ObjectMapper();
    private final AppleMusicTokenGenerator tokenGen;
    private final AppleMusicTokenStore     tokenStore;

    private long lastRequest = 0;

    public void pinMusicUserTokenForThread(String token) { MUT_OVERRIDE.set(token); }
    public void clearThreadToken()                       { MUT_OVERRIDE.remove(); }

    public String fetchCatalogPlaylistTitle(String storefront, String playlistId) throws IOException {
        JsonNode root = mapper.readTree(get(API_BASE + "/catalog/" + storefront + "/playlists/" + playlistId, false));
        return root.path("data").path(0).path("attributes").path("name").asText("Playlist " + playlistId);
    }

    public List<Track> fetchCatalogTracks(String storefront, String playlistId) throws IOException {
        return fetchTrackPages(API_BASE + "/catalog/" + storefront + "/playlists/" + playlistId + "/tracks", false);
    }

    public String fetchLibraryPlaylistTitle(String playlistId) throws IOException {
        JsonNode root = mapper.readTree(get(API_BASE + "/me/library/playlists/" + playlistId, true));
        return root.path("data").path(0).path("attributes").path("name").asText("Playlist " + playlistId);
    }

    public List<Track> fetchLibraryTracks(String playlistId) throws IOException {
        return fetchTrackPages(API_BASE + "/me/library/playlists/" + playlistId + "/tracks", true);
    }

    public List<PlaylistSummary> fetchUserPlaylists() throws IOException {
        List<PlaylistSummary> result = new ArrayList<>();
        String url = API_BASE + "/me/library/playlists?limit=100";
        while (url != null) {
            JsonNode root = mapper.readTree(get(url, true));
            for (JsonNode item : root.path("data")) {
                String id = item.path("id").asText("");
                if (id.isEmpty()) continue;
                String   name    = item.path("attributes").path("name").asText("");
                String   cover   = resolveCoverUrl(item.path("attributes").path("artwork"));
                result.add(new PlaylistSummary(id, name, 0, cover));
            }
            url = nextUrl(root);
        }
        return result;
    }

    // ── Private helpers ──────────────────────────────────────────────────────

    private List<Track> fetchTrackPages(String startUrl, boolean requireMut) throws IOException {
        List<Track> result = new ArrayList<>();
        String url = startUrl;
        while (url != null) {
            JsonNode root = mapper.readTree(get(url, requireMut));
            for (JsonNode node : root.path("data")) {
                JsonNode attrs = node.path("attributes");
                String   id    = node.path("id").asText("");
                if (id.isEmpty()) continue;
                String rawTitle  = attrs.path("name").asText("");
                String rawArtist = attrs.path("artistName").asText("");
                String date      = attrs.path("releaseDate").asText("");
                String year      = date.length() >= 4 ? date.substring(0, 4) : "";
                String trackUrl  = attrs.path("url").asText("https://music.apple.com/album/id/" + id);
                String[] ta = TrackNormalizer.extractFeaturing(TrackNormalizer.cleanTitle(rawTitle), rawArtist);
                result.add(new Track(id, ta[0], ta[1], year, trackUrl));
            }
            url = nextUrl(root);
        }
        return result;
    }

    private synchronized String get(String url, boolean requireMut) throws IOException {
        String devToken = tokenGen.getDeveloperToken();
        String mut      = MUT_OVERRIDE.get();
        if (mut == null) mut = tokenStore.getMusicUserToken();
        if (requireMut && (mut == null || mut.isBlank())) throw new AppleMusicAuthRequiredException();

        rateLimitWait();

        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .header("Authorization", "Bearer " + devToken)
                .header("Accept-Language", "en-US");
        if (mut != null && !mut.isBlank()) builder.header("Music-User-Token", mut);

        try {
            HttpResponse<String> resp = http.send(builder.GET().build(), HttpResponse.BodyHandlers.ofString());
            if (resp.statusCode() == 401 || resp.statusCode() == 403) throw new AppleMusicAuthRequiredException();
            if (resp.statusCode() != 200) {
                throw new IOException("Apple Music API error " + resp.statusCode() + ": " + resp.body());
            }
            return resp.body();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IOException("Request interrupted", e);
        }
    }

    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();
    }

    private static String nextUrl(JsonNode root) {
        JsonNode next = root.path("next");
        if (next.isMissingNode() || next.isNull()) return null;
        String rel = next.asText("");
        if (rel.isBlank()) return null;
        return rel.startsWith("http") ? rel : "https://api.music.apple.com" + rel;
    }

    private static String resolveCoverUrl(JsonNode artwork) {
        if (artwork.isMissingNode() || artwork.isNull()) return "";
        String tpl = artwork.path("url").asText("");
        if (tpl.isBlank()) return "";
        int w = artwork.path("width").asInt(300);
        int h = artwork.path("height").asInt(300);
        int size = Math.min(w, Math.min(h, 300));
        return tpl.replace("{w}", String.valueOf(size)).replace("{h}", String.valueOf(size));
    }
}
