package com.secureline.installer;

import android.app.Activity;
import android.content.pm.PackageInstaller;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public final class MainActivity extends Activity {
    private static final int TAB_APPS = 0;
    private static final int TAB_INSTALLED = 1;
    private static final long SEARCH_DEBOUNCE_MS = 180L;

    private final ExecutorService executor = Executors.newSingleThreadExecutor();
    private final Handler searchHandler = new Handler(Looper.getMainLooper());
    private final Runnable searchRender = this::render;
    private final List<AppEntry> apps = new ArrayList<>();
    private final List<AppState> states = new ArrayList<>();

    private LinearLayout content;
    private LinearLayout actionBar;
    private LinearLayout progressGroup;
    private EditText search;
    private TextView headerSummary;
    private TextView status;
    private TextView tabApps;
    private TextView tabInstalled;
    private ProgressBar progress;
    private Button installButton;
    private int activeTab = TAB_APPS;
    private boolean busy;
    private String query = "";

    private static final class AppState {
        final AppEntry app;
        boolean assetExists;
        long installedVersion = -1L;
        String installedVersionName = "";

        AppState(AppEntry app) { this.app = app; }
        boolean installed() { return installedVersion >= 0; }
        boolean metadataReady() { return app.hasVerificationMetadata(); }
        boolean current() { return installed(); }
        boolean readyToInstall() { return !installed() && assetExists && metadataReady(); }
        boolean actionable() { return readyToInstall(); }
        boolean blocked() { return assetExists && !metadataReady(); }
    }

    @Override protected void onCreate(Bundle state) {
        super.onCreate(state);
        setContentView(R.layout.activity_main);
        content = findViewById(R.id.content_container);
        actionBar = findViewById(R.id.action_bar);
        progressGroup = findViewById(R.id.progress_group);
        search = findViewById(R.id.search);
        headerSummary = findViewById(R.id.header_summary);
        status = findViewById(R.id.status);
        progress = findViewById(R.id.progress);
        installButton = findViewById(R.id.install_button);
        tabApps = findViewById(R.id.tab_apps);
        tabInstalled = findViewById(R.id.tab_installed);
        tabApps.setOnClickListener(v -> setTab(TAB_APPS));
        tabInstalled.setOnClickListener(v -> setTab(TAB_INSTALLED));
        installButton.setOnClickListener(v -> installAllAvailable());
        search.addTextChangedListener(new TextWatcher() {
            @Override public void beforeTextChanged(CharSequence s,int start,int count,int after) {}
            @Override public void onTextChanged(CharSequence s,int start,int before,int count) {
                query = s == null ? "" : s.toString().trim();
                searchHandler.removeCallbacks(searchRender);
                searchHandler.postDelayed(searchRender, SEARCH_DEBOUNCE_MS);
            }
            @Override public void afterTextChanged(Editable s) {}
        });
        loadCatalog();
    }

    @Override protected void onResume() {
        super.onResume();
        if (!apps.isEmpty() && !busy) { refreshInstalledState(); render(); }
    }

    @Override protected void onDestroy() {
        searchHandler.removeCallbacksAndMessages(null);
        executor.shutdownNow();
        super.onDestroy();
    }

    private void loadCatalog() {
        setBusy(true);
        showProgress(getString(R.string.scanning_catalog),0,1);
        executor.execute(() -> {
            try {
                List<AppEntry> loaded = loadApps();
                Set<String> assetNames = loadAssetNames();
                List<AppState> loadedStates = new ArrayList<>();
                for (AppEntry app : loaded) {
                    AppState s = new AppState(app);
                    s.assetExists = assetNames.contains(app.apk);
                    s.installedVersion = AppInstaller.installedVersionCode(this, app.packageName);
                    s.installedVersionName = AppInstaller.installedVersionName(this, app.packageName);
                    loadedStates.add(s);
                }
                runOnUiThread(() -> {
                    apps.clear(); apps.addAll(loaded);
                    states.clear(); states.addAll(loadedStates);
                    setBusy(false); hideProgress(); render();
                });
            } catch (Exception e) {
                runOnUiThread(() -> {
                    setBusy(false);
                    showProgress(getString(R.string.catalog_error),0,1);
                    renderEmpty(getString(R.string.catalog_error),getString(R.string.catalog_error_description));
                });
            }
        });
    }

    private Set<String> loadAssetNames() throws Exception {
        HashSet<String> names = new HashSet<>();
        String[] listed = getAssets().list("");
        if (listed != null) java.util.Collections.addAll(names, listed);
        return names;
    }

    private List<AppEntry> loadApps() throws Exception {
        try (InputStream in = getAssets().open("apps.json")) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buffer = new byte[16 * 1024]; int count;
            while ((count = in.read(buffer)) != -1) out.write(buffer,0,count);
            JSONObject root = new JSONObject(new String(out.toByteArray(), StandardCharsets.UTF_8));
            if (root.optInt("schemaVersion",1) > 2) throw new IllegalArgumentException("Unsupported catalog schema");
            JSONArray array = root.getJSONArray("apps");
            ArrayList<AppEntry> result = new ArrayList<>();
            for (int i=0;i<array.length();i++) {
                AppEntry entry = new AppEntry(array.getJSONObject(i));
                if (entry.isValid()) result.add(entry);
            }
            return result;
        }
    }

    private void refreshInstalledState() {
        for (AppState s : states) {
            s.installedVersion = AppInstaller.installedVersionCode(this, s.app.packageName);
            s.installedVersionName = AppInstaller.installedVersionName(this, s.app.packageName);
        }
    }

    private void setTab(int tab) {
        if (busy) return;
        activeTab = tab;
        query = "";
        search.setText("");
        styleTab(tabApps, tab == TAB_APPS);
        styleTab(tabInstalled, tab == TAB_INSTALLED);
        actionBar.setVisibility(tab == TAB_APPS ? View.VISIBLE : View.GONE);
        render();
    }

    private void styleTab(TextView tab, boolean selected) {
        tab.setBackgroundResource(selected ? R.drawable.lemenzo_tab_selected : R.drawable.lemenzo_tab_idle);
        tab.setTextColor(getColor(selected ? R.color.lemenzo_text : R.color.lemenzo_text_secondary));
        tab.setTypeface(null, selected ? Typeface.BOLD : Typeface.NORMAL);
    }

    private void render() {
        if (content == null) return;
        content.removeAllViews();
        ArrayList<AppState> visible = new ArrayList<>();
        String normalized = query.toLowerCase(Locale.ROOT);
        for (AppState s : states) {
            if (activeTab == TAB_INSTALLED && !s.installed()) continue;
            if (!normalized.isEmpty() && !s.app.searchText.contains(normalized)) continue;
            visible.add(s);
        }
        if (visible.isEmpty()) {
            renderEmpty(activeTab == TAB_INSTALLED ? getString(R.string.no_installed_apps) : getString(R.string.no_apps_found),
                    activeTab == TAB_INSTALLED ? getString(R.string.no_installed_apps_desc) : getString(R.string.no_apps_found_desc));
        } else {
            addSectionHeader(activeTab == TAB_INSTALLED ? getString(R.string.installed_apps) : getString(R.string.recommended_apps),
                    activeTab == TAB_INSTALLED ? getString(R.string.installed_apps_desc) : getString(R.string.recommended_apps_desc));
            for (AppState s : visible) content.addView(createAppCard(s));
        }
        updateHeaderSummary();
        updateInstallButton();
    }

    private View createAppCard(AppState s) {
        LinearLayout card = new LinearLayout(this);
        card.setOrientation(LinearLayout.VERTICAL);
        card.setBackgroundResource(R.drawable.lemenzo_card);
        card.setPadding(dp(16),dp(15),dp(16),dp(15));
        LinearLayout top = new LinearLayout(this);
        top.setOrientation(LinearLayout.HORIZONTAL); top.setGravity(Gravity.CENTER_VERTICAL);
        TextView avatar = text(initials(s.app.name),15,R.color.lemenzo_text,true);
        avatar.setGravity(Gravity.CENTER); avatar.setBackgroundResource(R.drawable.lemenzo_status_ready);
        top.addView(avatar,new LinearLayout.LayoutParams(dp(44),dp(44)));
        LinearLayout titleGroup = new LinearLayout(this); titleGroup.setOrientation(LinearLayout.VERTICAL);
        LinearLayout.LayoutParams tp = new LinearLayout.LayoutParams(0,ViewGroup.LayoutParams.WRAP_CONTENT,1f);
        tp.setMargins(dp(12),0,dp(8),0);
        titleGroup.addView(text(s.app.name,17,R.color.lemenzo_text,true));
        String meta = s.app.category;
        if (s.installed()) {
            String v = s.installedVersionName.isEmpty() ? getString(R.string.installed) : getString(R.string.version_value,s.installedVersionName);
            meta += "  •  " + v;
        } else if (!s.app.versionName.isEmpty()) {
            meta += "  •  " + getString(R.string.version_value,s.app.versionName);
        }
        TextView category = text(meta,12,R.color.lemenzo_blue_soft,false); category.setPadding(0,dp(3),0,0);
        titleGroup.addView(category); top.addView(titleGroup,tp); top.addView(makeStatusBadge(s)); card.addView(top);
        if (!s.app.description.isEmpty()) {
            TextView d = text(s.app.description,14,R.color.lemenzo_text_secondary,false); d.setPadding(0,dp(12),0,0); card.addView(d);
        }
        LinearLayout footer = new LinearLayout(this); footer.setOrientation(LinearLayout.HORIZONTAL); footer.setGravity(Gravity.CENTER_VERTICAL); footer.setPadding(0,dp(12),0,0);
        TextView source = text(s.app.source,11,R.color.lemenzo_text_muted,false);
        footer.addView(source,new LinearLayout.LayoutParams(0,ViewGroup.LayoutParams.WRAP_CONTENT,1f));
        if (s.actionable()) {
            TextView action = actionButton(getString(R.string.install));
            action.setOnClickListener(v -> installSingle(s)); footer.addView(action);
        }
        card.addView(footer);
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
        lp.setMargins(0,0,0,dp(10)); card.setLayoutParams(lp); return card;
    }

    private TextView makeStatusBadge(AppState s) {
        if (s.blocked()) return badge(getString(R.string.blocked),R.color.lemenzo_danger,R.drawable.lemenzo_status_danger);
        if (s.current()) return badge(getString(R.string.installed),R.color.lemenzo_success,R.drawable.lemenzo_status_success);
        if (s.readyToInstall()) return badge(getString(R.string.not_installed),R.color.lemenzo_blue_soft,R.drawable.lemenzo_status_ready);
        return badge(getString(R.string.offline_unavailable),R.color.lemenzo_text_muted,R.drawable.lemenzo_status_ready);
    }

    private TextView badge(String label,int color,int bg) {
        TextView b=text(label,11,color,true); b.setGravity(Gravity.CENTER); b.setBackgroundResource(bg); b.setPadding(dp(9),dp(5),dp(9),dp(5)); return b;
    }
    private TextView actionButton(String label) {
        TextView b=text(label,13,R.color.lemenzo_text,true); b.setGravity(Gravity.CENTER); b.setBackgroundResource(R.drawable.lemenzo_button_secondary); b.setPadding(dp(14),dp(9),dp(14),dp(9)); return b;
    }

    private void installSingle(AppState s) {
        if (busy || !s.actionable()) return;
        setBusy(true); showProgress(getString(R.string.installing_app,s.app.name),0,1);
        executor.execute(() -> {
            AppInstaller.Inspection inspection = AppInstaller.inspect(this,s.app);
            AppInstaller.InstallResult result;
            if (!inspection.trustedForInstall() || inspection.versionCode != s.app.versionCode) {
                result = new AppInstaller.InstallResult(false,PackageInstaller.STATUS_FAILURE_INVALID,
                        inspection.error.isEmpty()?"APK verification failed":inspection.error);
            } else result = AppInstaller.installAndWait(this,s.app,inspection);
            s.installedVersion = AppInstaller.installedVersionCode(this,s.app.packageName);
            s.installedVersionName = AppInstaller.installedVersionName(this,s.app.packageName);
            runOnUiThread(() -> { setBusy(false); showProgress(result.success?getString(R.string.install_success,s.app.name):getString(R.string.install_failure,s.app.name),result.success?1:0,1); render(); });
        });
    }

    private void installAllAvailable() {
        if (busy) return;
        ArrayList<AppState> queue = new ArrayList<>();
        for (AppState s : states) if (s.actionable()) queue.add(s);
        if (queue.isEmpty()) { showProgress(getString(R.string.nothing_to_install),1,1); return; }
        setBusy(true);
        executor.execute(() -> {
            int success=0,failed=0;
            for (int i=0;i<queue.size();i++) {
                AppState s=queue.get(i); int current=i;
                runOnUiThread(() -> showProgress(getString(R.string.installing_app,s.app.name),current,queue.size()));
                AppInstaller.Inspection inspection=AppInstaller.inspect(this,s.app);
                AppInstaller.InstallResult result;
                if (!inspection.trustedForInstall() || inspection.versionCode != s.app.versionCode)
                    result=new AppInstaller.InstallResult(false,PackageInstaller.STATUS_FAILURE_INVALID,inspection.error.isEmpty()?"APK verification failed":inspection.error);
                else result=AppInstaller.installAndWait(this,s.app,inspection);
                if (result.success) success++; else failed++;
                s.installedVersion=AppInstaller.installedVersionCode(this,s.app.packageName);
                s.installedVersionName=AppInstaller.installedVersionName(this,s.app.packageName);
            }
            final int ok=success,bad=failed;
            runOnUiThread(() -> {
                setBusy(false);
                String msg = bad==0 ? getString(R.string.batch_complete,ok) : ok==0 ? getString(R.string.batch_failed,bad) : getString(R.string.batch_partial,ok,bad);
                showProgress(msg,ok+bad,queue.size()); render();
            });
        });
    }

    private void addSectionHeader(String title,String desc) {
        TextView t=text(title,20,R.color.lemenzo_text,true); t.setPadding(dp(2),dp(6),dp(2),0); content.addView(t);
        TextView d=text(desc,13,R.color.lemenzo_text_secondary,false); d.setPadding(dp(2),dp(5),dp(2),dp(14)); content.addView(d);
    }
    private void renderEmpty(String title,String desc) {
        LinearLayout card=new LinearLayout(this); card.setOrientation(LinearLayout.VERTICAL); card.setGravity(Gravity.CENTER); card.setBackgroundResource(R.drawable.lemenzo_card); card.setPadding(dp(20),dp(30),dp(20),dp(30));
        TextView t=text(title,17,R.color.lemenzo_text,true); t.setGravity(Gravity.CENTER); card.addView(t);
        TextView d=text(desc,13,R.color.lemenzo_text_secondary,false); d.setGravity(Gravity.CENTER); d.setPadding(0,dp(8),0,0); card.addView(d); content.addView(card);
    }
    private void updateHeaderSummary() {
        int installed=0,notInstalled=0;
        for (AppState s:states) { if (s.installed()) installed++; else if (s.readyToInstall()) notInstalled++; }
        headerSummary.setText(getString(R.string.header_summary_offline,installed,notInstalled));
    }
    private void updateInstallButton() {
        int count=0; for (AppState s:states) if (s.actionable()) count++;
        installButton.setEnabled(!busy && count>0);
        installButton.setText(count>0 ? getResources().getQuantityString(R.plurals.install_available_count,count,count) : getString(R.string.nothing_to_install));
    }
    private void setBusy(boolean v) { busy=v; installButton.setEnabled(!v); search.setEnabled(!v); tabApps.setEnabled(!v); tabInstalled.setEnabled(!v); }
    private void showProgress(String msg,int current,int total) { progressGroup.setVisibility(View.VISIBLE); status.setText(msg); progress.setMax(Math.max(1,total)); progress.setProgress(Math.max(0,Math.min(current,Math.max(1,total)))); }
    private void hideProgress() { progressGroup.setVisibility(View.GONE); }
    private TextView text(String value,float size,int color,boolean bold) { TextView v=new TextView(this); v.setText(value); v.setTextSize(size); v.setTextColor(getColor(color)); if (bold) v.setTypeface(null,Typeface.BOLD); return v; }
    private String initials(String value) { if (value==null || value.trim().isEmpty()) return "A"; String[] parts=value.trim().split("\\s+"); StringBuilder out=new StringBuilder(); for (String part:parts) { if (!part.isEmpty()) out.append(Character.toUpperCase(part.charAt(0))); if (out.length()==2) break; } return out.length()==0?"A":out.toString(); }
    private int dp(int value) { return Math.round(value*getResources().getDisplayMetrics().density); }
}
