package org.openautonomousconnection.webserver.utils;

import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
import org.openautonomousconnection.webserver.api.WebPageContext;

import javax.net.ssl.HttpsURLConnection;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * Simple HTTPS -> OAC proxy helper.
 *
 * <p>Limitations:
 * <ul>
 *   <li>Does not rewrite HTML/CSS URLs. If you need full offline/proxied subresources,
 *       implement URL rewriting and route subresource paths through this proxy as well.</li>
 * </ul>
 */
public final class HttpsProxy {

    private static final int CONNECT_TIMEOUT_MS = 10_000;
    private static final int READ_TIMEOUT_MS = 25_000;
    private static final int MAX_REDIRECTS = 8;

    private HttpsProxy() {
    }

    /**
     * Fetches an HTTPS URL and returns it as a WebResponsePacket.
     *
     * @param ctx the current web page context (for optional user-agent forwarding)
     * @param url the target HTTPS URL
     * @return proxied response
     */
    public static WebResponsePacket proxyGet(WebPageContext ctx, String url) {
        try {
            return proxyGetInternal(ctx, url, 0);
        } catch (Exception e) {
            return new WebResponsePacket(
                    502,
                    "text/plain; charset=utf-8",
                    Map.of(),
                    ("Bad Gateway: " + e.getClass().getName() + ": " + e.getMessage()).getBytes(StandardCharsets.UTF_8)
            );
        }
    }

    private static WebResponsePacket proxyGetInternal(WebPageContext ctx, String url, int depth) throws Exception {
        if (depth > MAX_REDIRECTS) {
            return new WebResponsePacket(
                    508,
                    "text/plain; charset=utf-8",
                    Map.of(),
                    "Too many redirects".getBytes(StandardCharsets.UTF_8)
            );
        }

        URL target = new URL(url);
        HttpsURLConnection con = (HttpsURLConnection) target.openConnection();
        con.setInstanceFollowRedirects(false);
        con.setRequestMethod("GET");
        con.setConnectTimeout(CONNECT_TIMEOUT_MS);
        con.setReadTimeout(READ_TIMEOUT_MS);

        // Forward a user-agent if you have one (optional).
        String ua = null;
        if (ctx != null && ctx.request != null && ctx.request.getHeaders() != null) {
            ua = ctx.request.getHeaders().get("user-agent");
        }
        con.setRequestProperty("User-Agent", ua != null ? ua : "OAC-HttpsProxy/1.0");

        int code = con.getResponseCode();

        // Handle redirects manually to preserve content and avoid silent issues
        if (code == 301 || code == 302 || code == 303 || code == 307 || code == 308) {
            String location = con.getHeaderField("Location");
            if (location == null || location.isBlank()) {
                return new WebResponsePacket(
                        502,
                        "text/plain; charset=utf-8",
                        Map.of(),
                        ("Bad Gateway: redirect without Location (code=" + code + ")").getBytes(StandardCharsets.UTF_8)
                );
            }
            // Resolve relative redirects
            URL resolved = new URL(target, location);
            con.disconnect();
            return proxyGetInternal(ctx, resolved.toString(), depth + 1);
        }

        String contentType = con.getContentType();
        if (contentType == null || contentType.isBlank()) {
            contentType = "application/octet-stream";
        }

        byte[] body;
        try (InputStream in = (code >= 400 ? con.getErrorStream() : con.getInputStream())) {
            body = readAllBytes(in);
        } finally {
            con.disconnect();
        }

        Map<String, String> headers = new HashMap<>();
        return new WebResponsePacket(code, contentType, headers, body);
    }

    private static byte[] readAllBytes(InputStream in) throws Exception {
        if (in == null) return new byte[0];
        ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(1024, in.available()));
        byte[] buf = new byte[32 * 1024];
        int r;
        while ((r = in.read(buf)) != -1) {
            out.write(buf, 0, r);
        }
        return out.toByteArray();
    }
}
