/*
 * SoulFire
 * Copyright (C) 2026  AlexProgrammerDE
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package com.soulfiremc.server.account;

import net.lenni0451.commons.httpclient.HttpClient;
import net.lenni0451.commons.httpclient.requests.impl.GetRequest;
import net.lenni0451.commons.httpclient.utils.URLWrapper;
import net.raphimc.minecraftauth.msa.model.MsaApplicationConfig;
import net.raphimc.minecraftauth.msa.model.MsaToken;
import net.raphimc.minecraftauth.msa.request.MsaAuthCodeTokenRequest;
import net.raphimc.minecraftauth.msa.service.MsaAuthService;

import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/// MsaAuthService implementation that exchanges Microsoft Live browser cookies for an MsaToken.
/// Uses prompt=none to silently obtain an authorization code from existing session cookies,
/// then exchanges the code for tokens via the standard MsaAuthCodeTokenRequest.
public class CookieMsaAuthService extends MsaAuthService {
  private static final String DESKTOP_REDIRECT_URI = "https://login.live.com/oauth20_desktop.srf";

  private final String cookieHeader;

  public CookieMsaAuthService(HttpClient httpClient, MsaApplicationConfig applicationConfig, String cookieHeader) {
    super(httpClient, applicationConfig);
    this.cookieHeader = cookieHeader;
  }

  @Override
  public MsaToken acquireToken() throws IOException {
    var cookieApplicationConfig = applicationConfig.withRedirectUri(DESKTOP_REDIRECT_URI);
    var authorizeUrl = URLWrapper.ofURL(cookieApplicationConfig.getEnvironment().getAuthorizeUrl())
      .wrapQueryParameters()
      .addParameters(cookieApplicationConfig.getAuthCodeParameters())
      .addParameters(Map.of("prompt", "none"))
      .apply()
      .toURL();

    var request = new GetRequest(authorizeUrl);
    request.setHeader("Cookie", cookieHeader);
    request.setFollowRedirects(false);

    var response = httpClient.execute(request);
    var location = response.getFirstHeader("Location")
      .orElseThrow(() -> new IOException(
        "Silent sign-in returned no redirect (HTTP " + response.getStatusCode()
          + "). The cookies are likely invalid or do not belong to login.live.com."));

    var query = URLWrapper.ofURI(location).wrapQueryParameters();

    var code = query.getFirstValue("code");
    if (code.isPresent()) {
      return httpClient.executeAndHandle(
        new MsaAuthCodeTokenRequest(cookieApplicationConfig, code.get()));
    }

    var error = query.getFirstValue("error");
    if (error.isPresent()) {
      var description = query.getFirstValue("error_description")
        .map(d -> URLDecoder.decode(d, StandardCharsets.UTF_8).replace('+', ' '))
        .orElse("no description");
      throw new IOException("Microsoft rejected the silent sign-in: "
        + error.get() + " (" + description + "). "
        + "These cookies cannot get a token without interactive login.");
    }

    // No code and no error means Microsoft redirected to an interactive page
    // (login/consent/account picker), so the session cookies are not sufficient.
    var host = URLWrapper.ofURI(location).getHostOr("?");
    throw new IOException("Silent sign-in was redirected to an interactive page (" + host
      + ") instead of returning a token. The login.live.com session cookies are expired, "
      + "incomplete, or require interactive confirmation.");
  }
}
