#!/usr/bin/env python3
"""APK SDK Analyzer CLI.  Usage: apk-analyzer [OPTIONS] <apk_file|--batch dir>"""
import argparse, sys, time
from pathlib import Path

from apk_analyzer.analyzer.apk_parser import APKParser
from apk_analyzer.analyzer.dex_parser import DEXParser
from apk_analyzer.analyzer.manifest_parser import ManifestParser
from apk_analyzer.analyzer.native_parser import NativeParser
from apk_analyzer.analyzer.version_extractor import VersionExtractor
from apk_analyzer.signature_loader import SignatureLoader
from apk_analyzer.matcher import SDKMatcher
from apk_analyzer.reporter import Reporter
from apk_analyzer.models import AnalysisResult


def _default_signatures_dir() -> Path:
    if getattr(sys, "frozen", False):
        return Path(sys.executable).parent / "sdk_signatures"
    pkg_dir = Path(__file__).parent
    candidate = pkg_dir / "sdk_signatures"
    if candidate.exists():
        return candidate
    repo_root = pkg_dir.parent.parent
    candidate = repo_root / "sdk_signatures"
    if candidate.exists():
        return candidate
    return pkg_dir / "sdk_signatures"


def resolve_output_paths(apk_path: Path, args) -> tuple[Path, Path]:
    stem = apk_path.stem
    if args.output_dir:
        out_dir = Path(args.output_dir)
        default_json = out_dir / f"{stem}_report.json"
        default_html = out_dir / f"{stem}_report.html"
    else:
        default_json = apk_path.parent / f"{stem}_report.json"
        default_html = apk_path.parent / f"{stem}_report.html"
    out_json = Path(args.output_json) if args.output_json else default_json
    out_html = Path(args.output_html) if args.output_html else default_html
    return out_json, out_html


def _resolve_app_name(apk_path: Path) -> str:
    """Resolve app name using pyaxmlparser.APK - simple and direct."""
    try:
        import pyaxmlparser
        apk = pyaxmlparser.APK(str(apk_path))
        # pyaxmlparser.APK.application property directly returns the app name
        # It automatically handles resource resolution from resources.arsc
        app_name = apk.application
        return app_name if app_name else ""
    except Exception:
        return ""


def analyze_one(apk_path: Path, sig_dir: Path, args) -> AnalysisResult:
    categories = [c.strip() for c in args.categories.split(",")] if args.categories else None

    apk_info = APKParser(apk_path).parse()
    dex_data = DEXParser(apk_info.dex_files).parse()
    apk_info.class_count = len(dex_data.class_names)
    manifest = ManifestParser(apk_info.manifest_data).parse()
    native = NativeParser(apk_info.native_libs).parse()
    versions = VersionExtractor(dex_data, apk_info.metainf_versions).extract()

    # Resolve app name using pyaxmlparser.APK (automatically handles resource resolution)
    manifest.app_name = _resolve_app_name(apk_path)

    loader = SignatureLoader(sig_dir)
    signatures = loader.load(categories=categories)
    detected = SDKMatcher(signatures, dex_data, manifest, native, versions).match()

    return AnalysisResult(
        apk_path=str(apk_path),
        apk_info=apk_info,
        manifest=manifest,
        detected_sdks=detected,
        duration_seconds=0.0,
        signature_db_version=loader.db_version,
    )


def write_reports(result: AnalysisResult, out_json: Path, out_html: Path, fmt: str):
    tpl_path = Path(__file__).parent / "templates" / "report.html"
    if not tpl_path.exists():
        tpl_path = Path(__file__).parent.parent.parent / "templates" / "report.html"
    reporter = Reporter(result, str(tpl_path))
    if fmt in ("json", "both"):
        reporter.write_json(out_json)
    if fmt in ("html", "both"):
        reporter.write_html(out_html)


def _print_single_summary(result: AnalysisResult, duration: float):
    m = result.manifest
    app_name = m.app_name or m.package_name
    print(f"\n{'='*52}")
    print(f"  {app_name} ({m.package_name}) v{m.version_name}")
    print(f"  Detected: {len(result.detected_sdks)} SDKs  |  Time: {duration}s")
    for cat, count in sorted(result.summary_by_category().items(), key=lambda x: -x[1]):
        print(f"    {cat:<28} {count}")
    print(f"{'='*52}")


def _print_batch_summary(rows: list[dict]):
    print(f"\n{'='*72}")
    print(f"  BATCH SUMMARY — {len(rows)} APKs analyzed")
    print(f"{'='*72}")
    ok   = [r for r in rows if r["error"] is None]
    fail = [r for r in rows if r["error"] is not None]
    if ok:
        print(f"  {'APK':<30} {'App / Package':<34} {'SDKs':>4}  {'Time':>6}")
        print(f"  {'-'*30} {'-'*34} {'-'*4}  {'-'*6}")
        for r in ok:
            apk_name = Path(r["apk"]).name[:30]
            label = (r.get("app_name") or r.get("package") or "unknown")[:34]
            print(f"  {apk_name:<30} {label:<34} {r['sdk_count']:>4}  {r['duration']:>5.1f}s")
    if fail:
        print(f"\n  FAILED ({len(fail)}):")
        for r in fail:
            print(f"  [!] {Path(r['apk']).name}: {r['error']}")
    total_sdks = sum(r["sdk_count"] for r in ok if r["sdk_count"] is not None)
    total_time = sum(r["duration"] for r in rows)
    print(f"{'='*72}")
    print(f"  Total SDKs detected: {total_sdks}  |  Total time: {total_time:.1f}s")
    print(f"{'='*72}")


def run_single(apk_path: Path, sig_dir: Path, args):
    print(f"[*] Analyzing: {apk_path}")
    t0 = time.time()
    try:
        result = analyze_one(apk_path, sig_dir, args)
        duration = round(time.time() - t0, 2)
        result.duration_seconds = duration
        out_json, out_html = resolve_output_paths(apk_path, args)
        write_reports(result, out_json, out_html, args.format)
        if args.format in ("json", "both"):
            print(f"[+] JSON: {out_json}")
        if args.format in ("html", "both"):
            print(f"[+] HTML: {out_html}")
        _print_single_summary(result, duration)
    except Exception as e:
        print(f"[ERROR] {e}", file=sys.stderr)
        if args.verbose:
            import traceback; traceback.print_exc()
        sys.exit(1)


def run_batch(apk_paths: list[Path], sig_dir: Path, args):
    total = len(apk_paths)
    print(f"[*] Batch mode — {total} APK(s) found")
    if args.output_dir:
        print(f"[*] Output dir: {args.output_dir}")
    rows = []
    for i, apk_path in enumerate(apk_paths, 1):
        print(f"[{i}/{total}] {apk_path.name} ...", end=" ", flush=True)
        t0 = time.time()
        try:
            result = analyze_one(apk_path, sig_dir, args)
            duration = round(time.time() - t0, 2)
            result.duration_seconds = duration
            out_json, out_html = resolve_output_paths(apk_path, args)
            write_reports(result, out_json, out_html, args.format)
            sdk_count = len(result.detected_sdks)
            pkg = result.manifest.package_name or result.apk_info.package_name
            app_name = result.manifest.app_name
            print(f"OK  ({sdk_count} SDKs, {duration}s)")
            rows.append({"apk": str(apk_path), "package": pkg, "app_name": app_name,
                         "sdk_count": sdk_count, "duration": duration, "error": None})
        except Exception as e:
            duration = round(time.time() - t0, 2)
            print(f"FAILED  ({e})")
            rows.append({"apk": str(apk_path), "package": None, "app_name": None,
                         "sdk_count": None, "duration": duration, "error": str(e)})
    _print_batch_summary(rows)


def main():
    p = argparse.ArgumentParser(prog="apk-analyzer",
        description="Static SDK detection for Android APK files",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=(
            "Examples:\n"
            "  apk-analyzer app.apk\n"
            "  apk-analyzer app.apk --output-dir ./reports\n"
            "  apk-analyzer --batch /path/to/apks/\n"
            "  apk-analyzer --batch /path/to/apks/ --output-dir ./reports\n"
            "  apk-analyzer app.apk --categories push,ads"
        ))
    input_group = p.add_mutually_exclusive_group(required=True)
    input_group.add_argument("apk_file", nargs="?", default=None, help="Path to APK file")
    input_group.add_argument("--batch", metavar="DIR",
                             help="Directory containing APK files to analyze in batch")
    p.add_argument("--signatures-dir", default=None)
    p.add_argument("--output-dir", default=None)
    p.add_argument("--output-json", default=None)
    p.add_argument("--output-html", default=None)
    p.add_argument("--format", choices=["json", "html", "both"], default="both")
    p.add_argument("--categories", default=None)
    p.add_argument("--verbose", "-v", action="store_true")
    p.add_argument("--version", action="version",
                   version=f"apk-analyzer {__import__('importlib.metadata', fromlist=['version']).version('apk-analyzer')}")
    args = p.parse_args()

    if args.batch and (args.output_json or args.output_html):
        p.error("--output-json and --output-html cannot be used with --batch; use --output-dir instead")

    sig_dir = Path(args.signatures_dir) if args.signatures_dir else _default_signatures_dir()
    if not sig_dir.exists():
        print(f"[ERROR] Signatures directory not found: {sig_dir}", file=sys.stderr)
        print(f"  Tip: use --signatures-dir <path> to specify manually", file=sys.stderr)
        sys.exit(1)

    print(f"[*] APK SDK Analyzer")
    if args.batch:
        batch_dir = Path(args.batch)
        if not batch_dir.is_dir():
            print(f"[ERROR] Not a directory: {batch_dir}", file=sys.stderr); sys.exit(1)
        apk_paths = sorted(batch_dir.glob("*.apk"))
        if not apk_paths:
            print(f"[ERROR] No .apk files found in: {batch_dir}", file=sys.stderr); sys.exit(1)
        run_batch(apk_paths, sig_dir, args)
    else:
        apk_path = Path(args.apk_file)
        if not apk_path.exists():
            print(f"[ERROR] File not found: {apk_path}", file=sys.stderr); sys.exit(1)
        run_single(apk_path, sig_dir, args)


if __name__ == "__main__":
    main()
