package de.oaa.libredeck.web.controller;

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

import de.oaa.libredeck.web.config.TidalConfig;
import de.oaa.libredeck.web.service.tidal.TidalTokenStore;
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.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;

@Slf4j
@Controller
@RequestMapping("/tidal")
@RequiredArgsConstructor
public class TidalAuthController {

    private static final String AUTH_URL  = "https://login.tidal.com/authorize";
    private static final String TOKEN_URL = "https://auth.tidal.com/v1/oauth2/token";
    private static final String SCOPE     = "playlists.read collection.read user.read";

    private final TidalConfig     config;
    private final TidalTokenStore tokenStore;

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

    @GetMapping("/auth")
    public String auth(@RequestParam(name = "returnUrl", defaultValue = "") String returnUrl,
                       HttpServletRequest request) {
        String redirectUri    = buildRedirectUri(request);
        String codeVerifier   = generateCodeVerifier();
        String codeChallenge  = generateCodeChallenge(codeVerifier);

        tokenStore.setCodeVerifier(codeVerifier);
        tokenStore.setRedirectUri(redirectUri);

        String url = AUTH_URL
                + "?client_id="             + enc(config.getClientId())
                + "&response_type=code"
                + "&redirect_uri="          + enc(redirectUri)
                + "&scope="                 + enc(SCOPE)
                + "&code_challenge="        + enc(codeChallenge)
                + "&code_challenge_method=S256"
                + "&state="                 + enc(returnUrl);
        return "redirect:" + url;
    }

    @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("Tidal OAuth error: {}", error);
            return "redirect:/?tidalError=" + enc(error != null ? error : "unknown");
        }

        String returnUrl   = state; // state now carries only returnUrl
        String redirectUri = tokenStore.getRedirectUri();

        String codeVerifier = tokenStore.getCodeVerifier();
        if (codeVerifier == null || codeVerifier.isBlank()) {
            log.warn("Tidal callback: missing code_verifier in session");
            return "redirect:/?tidalError=missing_verifier";
        }

        try {
            String body = "grant_type=authorization_code"
                    + "&code="          + enc(code)
                    + "&client_id="     + enc(config.getClientId())
                    + "&redirect_uri="  + enc(redirectUri)
                    + "&code_verifier=" + enc(codeVerifier);
            if (!config.getClientSecret().isBlank()) {
                body += "&client_secret=" + enc(config.getClientSecret());
            }

            HttpRequest req = HttpRequest.newBuilder()
                    .uri(URI.create(TOKEN_URL))
                    .header("Content-Type", "application/x-www-form-urlencoded")
                    .POST(HttpRequest.BodyPublishers.ofString(body))
                    .build();

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

            if (json.has("error") || resp.statusCode() >= 400) {
                log.warn("Tidal token error ({}): {}", resp.statusCode(), resp.body());
                return "redirect:/?tidalError=" + 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);
            tokenStore.setCodeVerifier(null);

            // Store country from token response if present
            String country = json.path("user").path("countryCode").asText("");
            if (country.isBlank()) country = config.getCountryCode();
            tokenStore.setCountryCode(country);

            log.info("Tidal token acquired, country={}, expires in {}s", country, expiresIn);

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

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

    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) {
            base = scheme + "://" + host;
        } else {
            int port = request.getServerPort();
            boolean defaultPort = ("https".equals(scheme) && port == 443) || ("http".equals(scheme) && port == 80);
            base = scheme + "://" + host + (defaultPort ? "" : ":" + port);
        }
        return base + "/tidal/callback";
    }

    private static String generateCodeVerifier() {
        byte[] bytes = new byte[32];
        new SecureRandom().nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

    private static String generateCodeChallenge(String verifier) {
        try {
            byte[] digest = MessageDigest.getInstance("SHA-256")
                    .digest(verifier.getBytes(StandardCharsets.US_ASCII));
            return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
        } catch (Exception e) {
            throw new RuntimeException("SHA-256 not available", e);
        }
    }

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