package ru.otus.mobile.config;

import net.dongliu.apk.parser.ApkFile;
import net.dongliu.apk.parser.bean.ApkMeta;

import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Path;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

public final class ApkInfo {
    private final String packageName;
    private final String mainActivity;

    private ApkInfo(String packageName, String mainActivity) {
        this.packageName = packageName;
        this.mainActivity = mainActivity;
    }

    public String packageName() {
        return packageName;
    }

    public String mainActivity() {
        return mainActivity;
    }

    public static ApkInfo fromApk(Path apkPath) {
        try (ApkFile apkFile = new ApkFile(apkPath.toFile())) {
            ApkMeta meta = apkFile.getApkMeta();
            String pkg = meta.getPackageName();
            String activity = extractLaunchableActivity(apkFile.getManifestXml(), pkg);
            return new ApkInfo(pkg, activity);
        } catch (IOException e) {
            throw new IllegalStateException("Failed to parse APK: " + apkPath, e);
        }
    }

    private static String extractLaunchableActivity(String manifestXml, String pkg) {
        if (manifestXml == null || manifestXml.isBlank()) {
            return null;
        }
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
            try {
                factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            } catch (Exception ignored) {
                // Not all parsers support this; safe processing is still enabled.
            }
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(new InputSource(new StringReader(manifestXml)));

            String activity = findLaunchableActivity(doc, "activity", "name", pkg);
            if (activity != null) {
                return activity;
            }
            return findLaunchableActivity(doc, "activity-alias", "targetActivity", pkg);
        } catch (Exception e) {
            return null;
        }
    }

    private static String findLaunchableActivity(
            Document doc,
            String tagName,
            String nameAttr,
            String pkg
    ) {
        NodeList activities = doc.getElementsByTagName(tagName);
        for (int i = 0; i < activities.getLength(); i++) {
            Element activity = (Element) activities.item(i);
            if (!isLaunchable(activity)) {
                continue;
            }
            String raw = androidAttr(activity, nameAttr);
            if (raw == null || raw.isBlank()) {
                raw = androidAttr(activity, "name");
            }
            if (raw == null || raw.isBlank()) {
                continue;
            }
            return normalizeActivityName(raw, pkg);
        }
        return null;
    }

    private static boolean isLaunchable(Element activity) {
        NodeList filters = activity.getElementsByTagName("intent-filter");
        for (int i = 0; i < filters.getLength(); i++) {
            Element filter = (Element) filters.item(i);
            if (hasIntentAction(filter, "android.intent.action.MAIN")
                    && hasIntentCategory(filter, "android.intent.category.LAUNCHER")) {
                return true;
            }
        }
        return false;
    }

    private static boolean hasIntentAction(Element filter, String name) {
        NodeList actions = filter.getElementsByTagName("action");
        for (int i = 0; i < actions.getLength(); i++) {
            Element action = (Element) actions.item(i);
            if (name.equals(androidAttr(action, "name"))) {
                return true;
            }
        }
        return false;
    }

    private static boolean hasIntentCategory(Element filter, String name) {
        NodeList categories = filter.getElementsByTagName("category");
        for (int i = 0; i < categories.getLength(); i++) {
            Element category = (Element) categories.item(i);
            if (name.equals(androidAttr(category, "name"))) {
                return true;
            }
        }
        return false;
    }

    private static String androidAttr(Element element, String localName) {
        String value = element.getAttributeNS("http://schemas.android.com/apk/res/android", localName);
        if (value != null && !value.isBlank()) {
            return value;
        }
        value = element.getAttribute("android:" + localName);
        return value != null && !value.isBlank() ? value : null;
    }

    private static String normalizeActivityName(String raw, String pkg) {
        if (raw.startsWith(".")) {
            return pkg + raw;
        }
        if (raw.contains(".")) {
            return raw;
        }
        return pkg + "." + raw;
    }
}
