package de.oaa.libredeck.web.controller;

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

import de.oaa.libredeck.web.config.SpotifyConfig;
import de.oaa.libredeck.web.service.spotify.SpotifyTokenStore;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@Slf4j
@Controller
@RequestMapping("/spotify")
@RequiredArgsConstructor
public class SpotifyAuthController {

    private static final String AUTH_URL  = "https://accounts.spotify.com/authorize";
    private static final String TOKEN_URL = "https://accounts.spotify.com/api/token";
    private static final String SCOPE     = "playlist-read-private playlist-read-collaborative";

    private final SpotifyConfig     config;
    private final SpotifyTokenStore tokenStore;

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

    /** Redirects the browser to Spotify's authorization page. */
    @GetMapping("/auth")
    public String auth(@RequestParam(name = "returnUrl", defaultValue = "") String returnUrl,
                       HttpServletRequest request) {
        String redirectUri = buildRedirectUri(request);
        // Encode returnUrl + redirectUri together in state so callback can use both
        String state = returnUrl + "|" + redirectUri;
        String url = AUTH_URL
                + "?client_id="     + enc(config.getClientId())
                + "&response_type=code"
                + "&redirect_uri="  + enc(redirectUri)
                + "&scope="         + enc(SCOPE)
                + "&state="         + enc(state);
        return "redirect:" + url;
    }

    /** Spotify redirects here after the user grants access. */
    @GetMapping("/callback")
    public String callback(@RequestParam(name = "code",  required = false) String code,
                           @RequestParam(name = "error", required = false) String error,
                           @RequestParam(name = "state", defaultValue = "")  String state) {
        if (error != null || code == null) {
            log.warn("Spotify OAuth error: {}", error);
            return "redirect:/?spotifyError=" + enc(error != null ? error : "unknown");
        }

        // state = "returnUrl|redirectUri"
        String[] parts       = state.split("\\|", 2);
        String   returnUrl   = parts[0];
        String   redirectUri = parts.length > 1 ? parts[1] : config.getRedirectUri();

        try {
            String credentials = Base64.getEncoder().encodeToString(
                    (config.getClientId() + ":" + config.getClientSecret()).getBytes(StandardCharsets.UTF_8));

            HttpRequest req = HttpRequest.newBuilder()
                    .uri(URI.create(TOKEN_URL))
                    .header("Authorization", "Basic " + credentials)
                    .header("Content-Type", "application/x-www-form-urlencoded")
                    .POST(HttpRequest.BodyPublishers.ofString(
                            "grant_type=authorization_code"
                            + "&code=" + enc(code)
                            + "&redirect_uri=" + enc(redirectUri)))
                    .build();

            HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString());
            JsonNode json = mapper.readTree(resp.body());

            if (json.has("error")) {
                log.warn("Spotify token error: {}", resp.body());
                return "redirect:/?spotifyError=" + enc(json.path("error").asText("token_error"));
            }

            tokenStore.setAccessToken(json.path("access_token").asText());
            tokenStore.setRefreshToken(json.path("refresh_token").asText(""));
            int expiresIn = json.path("expires_in").asInt(3600);
            tokenStore.setExpiresAt(System.currentTimeMillis() + (expiresIn - 60) * 1000L);

            log.info("Spotify token acquired for session, expires in {}s", expiresIn);

        } catch (Exception e) {
            log.error("Spotify token exchange failed", e);
            return "redirect:/?spotifyError=token_exchange_failed";
        }

        if (!returnUrl.isBlank()) {
            return "redirect:/?spotifyUrl=" + enc(returnUrl) + "#generator";
        }
        return "redirect:/#generator";
    }

    /** Derives the callback URI from the current request — works for localhost and production without config. */
    private static String buildRedirectUri(HttpServletRequest request) {
        boolean behindProxy = request.getHeader("X-Forwarded-Proto") != null;
        String scheme = behindProxy ? request.getHeader("X-Forwarded-Proto") : request.getScheme();
        String host = request.getHeader("X-Forwarded-Host");
        if (host == null) host = request.getServerName();
        String base;
        if (behindProxy) {
            // External port is managed by the proxy; never append it
            base = scheme + "://" + host;
        } else {
            int port = request.getServerPort();
            boolean defaultPort = (scheme.equals("https") && port == 443) || (scheme.equals("http") && port == 80);
            base = scheme + "://" + host + (defaultPort ? "" : ":" + port);
        }
        return base + "/spotify/callback";
    }

    private static String enc(String s) {
        return URLEncoder.encode(s, StandardCharsets.UTF_8);
    }
}
