package com.foxx.androidcast.ota;

/*********************************************************************
 * OtaApkInstaller.java
 * Created at: Wed 20 May 2026 14:31:55 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class OtaApkInstaller
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 143 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	ea6b467b9b8c4ee24c26d349772228aef7595f259e996a280813a56f7cbf1166
**********************************************************************/
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;

import androidx.core.content.FileProvider;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;

/** Downloads an APK and launches the package installer. */
public final class OtaApkInstaller {
    private static final String TAG = "OtaApkInstaller";
    private static final int CONNECT_TIMEOUT_MS = 15_000;
    private static final int READ_TIMEOUT_MS = 300_000;

    public interface ProgressListener {
        void onProgress(long downloadedBytes, long totalBytes);
    }

    private OtaApkInstaller() {}

    /** Resolves a verified APK: HTTP bundle when present, else legacy per-artifact download. */
    public static File downloadApk(Context context, OtaManifest manifest, ProgressListener listener)
            throws Exception {
        if (manifest == null) {
            throw new IllegalArgumentException("Missing manifest");
        }
        if (manifest.hasHttpBundle()) {
            return OtaBundleInstaller.downloadApkFromBundle(context, manifest, listener);
        }
        return downloadApkLegacy(context, manifest, listener);
    }

    static File downloadApkLegacy(Context context, OtaManifest manifest, ProgressListener listener)
            throws Exception {
        if (manifest.apkUrl.isEmpty()) {
            throw new IllegalArgumentException("Missing apkUrl");
        }
        File dir = new File(context.getCacheDir(), "ota");
        if (!dir.exists() && !dir.mkdirs()) {
            throw new IllegalStateException("Cannot create OTA cache dir");
        }
        File out = new File(dir, "update-" + manifest.effectiveVersionCode() + ".apk");
        if (out.exists() && !out.delete()) {
            throw new IllegalStateException("Cannot replace cached APK");
        }
        String scheme = OtaJsonFetch.schemeOf(manifest.apkUrl);
        if ("mqtt".equals(scheme) || "mqtts".equals(scheme)) {
            byte[] payload = OtaJsonFetch.getBytes(manifest.apkUrl, 512 * 1024 * 1024);
            long total = payload.length;
            try (InputStream in = new BufferedInputStream(new ByteArrayInputStream(payload));
                    FileOutputStream fos = new FileOutputStream(out)) {
                byte[] buf = new byte[32 * 1024];
                long downloaded = 0;
                int n;
                while ((n = in.read(buf)) != -1) {
                    fos.write(buf, 0, n);
                    downloaded += n;
                    if (listener != null) {
                        listener.onProgress(downloaded, total);
                    }
                }
            }
        } else {
            HttpURLConnection conn = (HttpURLConnection) new URL(manifest.apkUrl).openConnection();
            conn.setConnectTimeout(CONNECT_TIMEOUT_MS);
            conn.setReadTimeout(READ_TIMEOUT_MS);
            conn.setInstanceFollowRedirects(true);
            int code = conn.getResponseCode();
            if (code < 200 || code >= 300) {
                conn.disconnect();
                throw new IllegalStateException("Download HTTP " + code);
            }
            long total = conn.getContentLengthLong();
            try (InputStream in = new BufferedInputStream(conn.getInputStream());
                    FileOutputStream fos = new FileOutputStream(out)) {
                byte[] buf = new byte[32 * 1024];
                long downloaded = 0;
                int n;
                while ((n = in.read(buf)) != -1) {
                    fos.write(buf, 0, n);
                    downloaded += n;
                    if (listener != null) {
                        listener.onProgress(downloaded, total);
                    }
                }
            } finally {
                conn.disconnect();
            }
        }
        if (!manifest.sha256Hex.isEmpty()) {
            String actual = sha256Hex(out);
            if (!manifest.sha256Hex.equalsIgnoreCase(actual)) {
                out.delete();
                throw new IllegalStateException("APK checksum mismatch");
            }
        }
        Log.i(TAG, "Downloaded OTA APK " + out.length() + " bytes -> " + out.getName());
        return out;
    }

    public static void launchInstall(Context context, File apkFile) {
        Uri uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", apkFile);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }

    static String sha256Hex(File file) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        try (BufferedInputStream in = new BufferedInputStream(new java.io.FileInputStream(file))) {
            byte[] buf = new byte[8192];
            int n;
            while ((n = in.read(buf)) != -1) {
                md.update(buf, 0, n);
            }
        }
        byte[] digest = md.digest();
        StringBuilder sb = new StringBuilder(digest.length * 2);
        for (byte b : digest) {
            sb.append(String.format("%02x", b & 0xff));
        }
        return sb.toString();
    }
}
