package io.github.madjacket.kat5200;

import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * Minimal HTTP(S) downloader for the ROM-set feature, called from native code
 * (the __ANDROID__ backend of dl_download_to_file in menu_setup.c) over JNI.
 * Uses the platform HTTP stack and system TLS trust store, so the native side
 * ships no curl/OpenSSL.  Progress is pushed back to native via nativeProgress().
 *
 * Kept API-21 safe (minSdk 21): only classic HttpURLConnection / stream APIs.
 */
public final class KatDownloader {

    private static final String TAG = "kat5200";
    private static final int MAX_ATTEMPTS = 4;       // mirrors DL_MAX_ATTEMPTS
    private static final int MAX_REDIRECTS = 5;
    private static final int CONNECT_TIMEOUT_MS = 15000;
    private static final int READ_TIMEOUT_MS = 30000;
    private static final String USER_AGENT = "kat5200/1.0.0";

    private KatDownloader() {}

    /** Native progress sink (1..100), implemented in libkat5200.so. */
    private static native void nativeProgress(int percent);

    /**
     * Download {@code urlStr} to {@code outPath}.  Returns true only on a clean
     * HTTP 200 plus a complete body.  HTTPS only (cleartext is blocked by the
     * platform on modern Android anyway).  Runs synchronously on the caller's
     * (native) download thread.
     */
    public static boolean downloadToFile(String urlStr, String outPath) {
        for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
            if (attempt > 1) {
                try {
                    Thread.sleep(1000L * (attempt - 1));     // linear backoff
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    return false;
                }
                Log.i(TAG, "ROM download: retrying (attempt " + attempt + " of " + MAX_ATTEMPTS + ")");
            }

            int outcome = attemptDownload(urlStr, outPath);  // 1 ok, 0 retry, -1 fatal
            if (outcome > 0) return true;
            if (outcome < 0) return false;
        }
        Log.e(TAG, "ROM download: giving up after repeated errors");
        return false;
    }

    /** @return 1 success, 0 transient (retry), -1 fatal (stop). */
    private static int attemptDownload(String urlStr, String outPath) {
        HttpURLConnection conn = null;
        try {
            String current = urlStr;

            for (int redirect = 0; redirect <= MAX_REDIRECTS; redirect++) {
                if (!current.regionMatches(true, 0, "https://", 0, 8)) {
                    Log.e(TAG, "ROM download: refusing non-HTTPS URL: " + current);
                    return -1;
                }

                conn = (HttpURLConnection) new URL(current).openConnection();
                conn.setConnectTimeout(CONNECT_TIMEOUT_MS);
                conn.setReadTimeout(READ_TIMEOUT_MS);
                conn.setRequestProperty("User-Agent", USER_AGENT);
                conn.setInstanceFollowRedirects(false);      // followed manually below
                conn.connect();

                int code = conn.getResponseCode();

                if (code == HttpURLConnection.HTTP_MOVED_PERM
                        || code == HttpURLConnection.HTTP_MOVED_TEMP
                        || code == HttpURLConnection.HTTP_SEE_OTHER
                        || code == 307 || code == 308) {
                    String loc = conn.getHeaderField("Location");
                    conn.disconnect();
                    conn = null;
                    if (loc == null) {
                        Log.e(TAG, "ROM download: redirect with no Location header");
                        return -1;
                    }
                    current = new URL(new URL(current), loc).toString();  // resolve relative
                    continue;
                }

                if (code != HttpURLConnection.HTTP_OK) {
                    Log.e(TAG, "ROM download: server returned HTTP " + code
                            + (code >= 500 ? " (will retry)" : ""));
                    return (code >= 500) ? 0 : -1;
                }

                // HTTP 200 -- stream the body to the output file.
                long total = -1;
                String clen = conn.getHeaderField("Content-Length");
                if (clen != null) {
                    try {
                        total = Long.parseLong(clen.trim());
                    } catch (NumberFormatException nfe) {
                        total = -1;
                    }
                }

                File outFile = new File(outPath);
                File parent = outFile.getParentFile();
                if (parent != null) parent.mkdirs();

                byte[] buf = new byte[65536];
                long received = 0;
                int lastPct = 0;
                InputStream in = conn.getInputStream();
                OutputStream out = new FileOutputStream(outFile);
                try {
                    int n;
                    while ((n = in.read(buf)) > 0) {
                        out.write(buf, 0, n);
                        received += n;
                        if (total > 0) {
                            int pct = (int) (received * 99 / total) + 1;
                            if (pct != lastPct) {     // throttle JNI calls
                                nativeProgress(pct);
                                lastPct = pct;
                            }
                        }
                    }
                } finally {
                    try { out.close(); } catch (IOException ignore) {}
                    try { in.close(); } catch (IOException ignore) {}
                }
                return 1;
            }

            Log.e(TAG, "ROM download: too many redirects");
            return -1;

        } catch (IOException ioe) {
            Log.e(TAG, "ROM download: transfer failed: " + ioe.getMessage());
            return 0;     // network blip -> retry
        } finally {
            if (conn != null) conn.disconnect();
        }
    }
}
