#!/usr/bin/env python3
"""AppInsight CLI - Scrape and filter App Store reviews from the terminal.

Usage:
    python3 cli.py search "WhatsApp"
    python3 cli.py reviews 310633997 --stars 2 --days 30
    python3 cli.py reviews 310633997 --keywords crash,freeze --format json
"""

import argparse
import sys

import requests

from .scrapers.appstore import search_app, lookup_app, fetch_reviews
from .output.filters import apply_filters
from .output.formatters import to_json, to_csv, to_markdown, to_text, summary_stats
from .commands.analyzer import analyze, check_ollama, list_models
from .commands.compare import compare_apps
from .commands.version_diff import version_diff
from .commands.trend import trend as trend_report
from .setup import cmd_setup, AGENTS

STORES = ["apple", "google"]

# Map common country names/aliases to ISO 2-letter codes for user convenience
COUNTRY_ALIASES = {
    # Full names
    "united states": "us", "usa": "us", "america": "us",
    "united kingdom": "gb", "uk": "gb", "england": "gb", "britain": "gb",
    "germany": "de", "deutschland": "de",
    "france": "fr", "french": "fr",
    "japan": "jp", "japanese": "jp",
    "australia": "au",
    "canada": "ca",
    "netherlands": "nl", "holland": "nl", "dutch": "nl",
    "brazil": "br", "brasil": "br",
    "south korea": "kr", "korea": "kr",
    "china": "cn",
    "india": "in",
    "italy": "it", "italian": "it",
    "spain": "es", "spanish": "es",
    "mexico": "mx",
    "russia": "ru", "russian": "ru",
    "turkey": "tr", "turkish": "tr",
    "sweden": "se", "swedish": "se",
    "norway": "no", "norwegian": "no",
    "denmark": "dk", "danish": "dk",
    "finland": "fi", "finnish": "fi",
    "poland": "pl", "polish": "pl",
    "switzerland": "ch", "swiss": "ch",
    "austria": "at",
    "belgium": "be",
    "portugal": "pt", "portuguese": "pt",
    "ireland": "ie",
    "new zealand": "nz",
    "singapore": "sg",
    "hong kong": "hk",
    "taiwan": "tw",
    "thailand": "th", "thai": "th",
    "indonesia": "id",
    "malaysia": "my",
    "philippines": "ph",
    "vietnam": "vn",
    "saudi arabia": "sa",
    "united arab emirates": "ae", "uae": "ae",
    "israel": "il",
    "south africa": "za",
    "argentina": "ar",
    "colombia": "co",
    "chile": "cl",
    "peru": "pe",
    "czech republic": "cz", "czechia": "cz",
    "romania": "ro",
    "hungary": "hu",
    "greece": "gr",
    "ukraine": "ua",
    "egypt": "eg",
}

# Valid ISO country codes for App Store (not exhaustive, but covers major stores)
VALID_COUNTRY_CODES = {
    "us", "gb", "de", "fr", "jp", "au", "ca", "nl", "br", "kr", "cn", "in",
    "it", "es", "mx", "ru", "tr", "se", "no", "dk", "fi", "pl", "ch", "at",
    "be", "pt", "ie", "nz", "sg", "hk", "tw", "th", "id", "my", "ph", "vn",
    "sa", "ae", "il", "za", "ar", "co", "cl", "pe", "cz", "ro", "hu", "gr",
    "ua", "eg",
}


def resolve_country(value: str) -> str:
    """Resolve a country name or alias to a 2-letter ISO code.

    Accepts: ISO codes (us, de, gb), full names (germany, united states),
    and common aliases (uk, usa, holland).
    """
    lowered = value.lower().strip()

    # Try alias lookup first (handles uk->gb, usa->us, etc.)
    if lowered in COUNTRY_ALIASES:
        return COUNTRY_ALIASES[lowered]

    # Already a valid 2-letter code
    if len(lowered) == 2 and lowered.isalpha():
        return lowered

    # Fuzzy: check if input is a prefix of any known alias
    matches = [code for name, code in COUNTRY_ALIASES.items() if name.startswith(lowered)]
    if len(matches) == 1:
        return matches[0]

    # Not found - show helpful suggestions
    common_examples = "us, gb/uk, de/germany, fr/france, jp/japan, au, ca, nl, br, kr"
    print(f"Warning: Unknown country '{value}'. Using as-is.", file=sys.stderr)
    print(f"  Common values: {common_examples}", file=sys.stderr)
    print(f"  Use 2-letter ISO codes or country names (e.g. --country germany).", file=sys.stderr)
    return lowered


def cmd_search(args):
    """Search the App Store or Google Play by name."""
    try:
        if args.store == "google":
            from .scrapers.google_play import search_play
            apps = search_play(args.query, country=args.country, limit=args.limit)
        else:
            apps = search_app(args.query, country=args.country, limit=args.limit)
    except ImportError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)
    except requests.RequestException as e:
        print(f"Error: Failed to search: {e}", file=sys.stderr)
        sys.exit(1)

    if not apps:
        print(f"No apps found for '{args.query}'", file=sys.stderr)
        sys.exit(1)

    if args.format == "json":
        print(to_json_apps(apps))
    else:
        print(f"{'ID':<12} {'Rating':>6} {'Reviews':>10}  Name")
        print("-" * 60)
        for a in apps:
            print(f"{a.app_id:<12} {a.avg_rating:>5.1f}⭐ {a.rating_count:>10,}  {a.name}")


def cmd_reviews(args):
    """Fetch and filter reviews."""
    app_id = args.app_id

    try:
        if args.store == "google":
            from .scrapers.google_play import lookup_play, fetch_play_reviews
            app = lookup_play(app_id, country=args.country)
            reviews = fetch_play_reviews(app_id, country=args.country, pages=args.pages)
        else:
            try:
                int(app_id)
            except ValueError:
                print(f"Error: Apple App Store IDs must be numeric (got '{app_id}'). "
                      "Did you mean --store google?", file=sys.stderr)
                sys.exit(1)
            try:
                app = lookup_app(app_id, country=args.country)
            except requests.RequestException:
                app = None
            reviews = fetch_reviews(app_id, country=args.country, pages=args.pages)
    except ImportError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

    if app:
        print(f"Fetching reviews for: {app.name} (by {app.developer})", file=sys.stderr)
    else:
        print(f"Fetching reviews for app ID: {app_id}", file=sys.stderr)
    print(f"Fetched {len(reviews)} reviews", file=sys.stderr)

    if not reviews:
        print("No reviews found for this app. The app may be too new, too small, "
              "or not available in this country's store. "
              "Try a different --country (e.g. --country us).", file=sys.stderr)
        sys.exit(0)

    # Apply filters
    keywords = args.keywords.split(",") if args.keywords else None
    reviews = apply_filters(
        reviews,
        max_rating=args.stars,
        min_rating=args.min_stars,
        keywords=keywords,
        days=args.days,
        version=args.version,
        sort_by=args.sort,
    )
    print(f"After filtering: {len(reviews)} reviews", file=sys.stderr)

    # Stats always go to stderr so they don't pollute the data output
    if args.stats:
        print("\n" + summary_stats(reviews), file=sys.stderr)

    # Output
    if args.format == "json":
        print(to_json(reviews))
    elif args.format == "csv":
        print(to_csv(reviews), end="")
    elif args.format == "markdown":
        print(to_markdown(reviews))
    else:
        print(to_text(reviews))


def cmd_analyze(args):
    """Fetch reviews, filter, and analyze with a local LLM via Ollama."""
    # Show available models if requested (before Ollama check so it works even as a quick probe)
    if args.list_models:
        if not check_ollama():
            print("Ollama is not running. Start it with: ollama serve", file=sys.stderr)
            print("Install from: https://ollama.com/download", file=sys.stderr)
            sys.exit(1)
        models = list_models()
        if models:
            print("Available Ollama models:")
            for m in models:
                print(f"  {m}")
        else:
            print("No models found. Pull one with: ollama pull qwen3.5:4b")
        return

    app_id = args.app_id

    # Check Ollama
    if not check_ollama():
        print("Error: Ollama is not running. Start it with: ollama serve", file=sys.stderr)
        print("Install from: https://ollama.com/download", file=sys.stderr)
        sys.exit(1)

    # Fetch
    try:
        if args.store == "google":
            from .scrapers.google_play import lookup_play, fetch_play_reviews
            app = lookup_play(args.app_id, country=args.country)
            reviews = fetch_play_reviews(args.app_id, country=args.country, pages=args.pages)
        else:
            try:
                int(args.app_id)
            except ValueError:
                print(f"Error: Apple App Store IDs must be numeric (got '{args.app_id}'). "
                      "Did you mean --store google?", file=sys.stderr)
                sys.exit(1)
            try:
                app = lookup_app(app_id, country=args.country)
            except requests.RequestException:
                app = None
            reviews = fetch_reviews(app_id, country=args.country, pages=args.pages)
    except ImportError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

    if app:
        print(f"Fetching reviews for: {app.name} (by {app.developer})", file=sys.stderr)
    else:
        print(f"Fetching reviews for app ID: {app_id}", file=sys.stderr)
    print(f"Fetched {len(reviews)} reviews", file=sys.stderr)

    if not reviews:
        print("No reviews found for this app. The app may be too new, too small, "
              "or not available in this country's store. "
              "Try a different --country (e.g. --country us).", file=sys.stderr)
        sys.exit(0)

    # Apply filters
    keywords = args.keywords.split(",") if args.keywords else None
    reviews = apply_filters(
        reviews,
        max_rating=args.stars,
        min_rating=args.min_stars,
        keywords=keywords,
        days=args.days,
        version=args.version,
        sort_by=args.sort,
    )
    print(f"After filtering: {len(reviews)} reviews", file=sys.stderr)

    if not reviews:
        print("No reviews match the given filters. Try relaxing your filters.", file=sys.stderr)
        sys.exit(0)

    # Stats
    if args.stats:
        print("\n" + summary_stats(reviews), file=sys.stderr)

    # Analyze
    print(f"\nAnalyzing with {args.model} (mode: {args.mode})...\n", file=sys.stderr)
    result = analyze(reviews, mode=args.mode, model=args.model, stream=True)

    # Also print to stdout (streaming went to stderr)
    print(result)


def to_json_apps(apps):
    """Quick JSON serializer for app search results."""
    import json
    return json.dumps([a.to_dict() for a in apps], indent=2, ensure_ascii=False)


def cmd_compare(args):
    """Compare reviews across multiple apps."""
    if args.store == "apple":
        for aid in args.app_ids:
            try:
                int(aid)
            except ValueError:
                print(f"Error: Apple App Store IDs must be numeric (got '{aid}'). "
                      "Did you mean --store google?", file=sys.stderr)
                sys.exit(1)
    keywords = args.keywords.split(",") if args.keywords else None
    try:
        result = compare_apps(
            app_ids=args.app_ids,
            country=args.country,
            pages=args.pages,
            max_rating=args.stars,
            min_rating=args.min_stars,
            keywords=keywords,
            days=args.days,
            sort_by=args.sort,
            store=args.store,
            format=args.format,
        )
    except ImportError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)
    print(result)


def cmd_version_diff(args):
    """Compare sentiment between app versions."""
    if args.store == "apple":
        try:
            int(args.app_id)
        except ValueError:
            print(f"Error: Apple App Store IDs must be numeric (got '{args.app_id}'). "
                  "Did you mean --store google?", file=sys.stderr)
            sys.exit(1)
    keywords = args.keywords.split(",") if args.keywords else None
    try:
        result = version_diff(
            app_id=args.app_id,
            country=args.country,
            pages=args.pages,
            old_version=args.old,
            new_version=args.new,
            max_rating=args.stars,
            min_rating=args.min_stars,
            keywords=keywords,
            days=args.days,
            store=args.store,
            format=args.format,
        )
    except ImportError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)
    print(result)


def cmd_trend(args):
    """Show rating trend over time."""
    if args.store == "apple":
        try:
            int(args.app_id)
        except ValueError:
            print(f"Error: Apple App Store IDs must be numeric (got '{args.app_id}'). "
                  "Did you mean --store google?", file=sys.stderr)
            sys.exit(1)
    keywords = args.keywords.split(",") if args.keywords else None
    try:
        result = trend_report(
            app_id=args.app_id,
            country=args.country,
            pages=args.pages,
            period=args.period,
            max_rating=args.stars,
            min_rating=args.min_stars,
            keywords=keywords,
            days=args.days,
            store=args.store,
            format=args.format,
        )
    except ImportError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)
    print(result)


def main():
    # Shared arguments that should work both before and after the subcommand.
    # argparse parents= copies these into each subparser so
    # `appstore-reviews --country us reviews ...` and
    # `appstore-reviews reviews ... --country us` both work.
    # Using SUPPRESS as default so the subparser doesn't overwrite
    # the parent parser's value when the flag isn't repeated.
    shared = argparse.ArgumentParser(add_help=False)
    shared.add_argument("--country", default=argparse.SUPPRESS,
                        help="Country code or name (default: us). "
                             "Examples: us, gb, de, germany, japan, uk")
    shared.add_argument("--store", choices=STORES, default=argparse.SUPPRESS,
                        help="App store: apple (default) or google")

    parser = argparse.ArgumentParser(
        prog="appstore-reviews",
        description="Scrape and filter App Store & Google Play reviews. Built for coding agents.",
    )
    parser.add_argument("--country", default="us",
                        help="Country code or name (default: us). "
                             "Examples: us, gb, de, germany, japan, uk")
    parser.add_argument("--store", choices=STORES, default="apple",
                        help="App store: apple (default) or google")

    sub = parser.add_subparsers(dest="command", required=True)

    # --- search ---
    p_search = sub.add_parser("search", help="Search for an app by name", parents=[shared])
    p_search.add_argument("query", help="App name to search for")
    p_search.add_argument("--limit", type=int, default=5, help="Max results (default: 5)")
    p_search.add_argument("--format", choices=["table", "json"], default="table")

    # --- reviews ---
    p_reviews = sub.add_parser("reviews", help="Fetch and filter reviews", parents=[shared])
    p_reviews.add_argument("app_id", help="Numeric App Store ID or Google Play package name")
    p_reviews.add_argument("--stars", type=int, default=None, choices=range(1, 6),
                           help="Max star rating to include (e.g. 2 = 1-2 stars)", metavar="STARS")
    p_reviews.add_argument("--min-stars", type=int, default=None, choices=range(1, 6),
                           help="Min star rating to include (e.g. 3 = 3+ stars)", metavar="STARS")
    p_reviews.add_argument("--days", type=int, default=None, help="Only reviews from the last N days")
    p_reviews.add_argument("--keywords", default=None, help="Comma-separated keywords to filter by")
    p_reviews.add_argument("--version", default=None, help="Filter by app version")
    p_reviews.add_argument("--pages", type=int, default=3, choices=range(1, 11),
                           help="Pages to fetch, 1-10 (default: 3)", metavar="PAGES")
    p_reviews.add_argument("--format", choices=["text", "json", "csv", "markdown"], default="text")
    p_reviews.add_argument("--sort", choices=["date", "rating", "votes"], default=None,
                           help="Sort order: date (newest), rating (lowest), votes (most helpful)")
    p_reviews.add_argument("--stats", action="store_true", help="Show rating distribution stats")

    # --- analyze ---
    p_analyze = sub.add_parser("analyze", help="Fetch reviews and analyze with a local LLM (Ollama)", parents=[shared])
    p_analyze.add_argument("app_id", nargs="?", default=None,
                           help="Numeric App Store ID or Google Play package name")
    p_analyze.add_argument("--mode", choices=["summary", "gaps", "bugs"], default="summary",
                           help="Analysis mode: summary, gaps (feature gaps), bugs (technical issues)")
    p_analyze.add_argument("--model", default="qwen3.5:4b", help="Ollama model to use (default: qwen3.5:4b)")
    p_analyze.add_argument("--stars", type=int, default=None, choices=range(1, 6),
                           help="Max star rating to include", metavar="STARS")
    p_analyze.add_argument("--min-stars", type=int, default=None, choices=range(1, 6),
                           help="Min star rating to include", metavar="STARS")
    p_analyze.add_argument("--days", type=int, default=None, help="Only reviews from the last N days")
    p_analyze.add_argument("--keywords", default=None, help="Comma-separated keywords to filter by")
    p_analyze.add_argument("--version", default=None, help="Filter by app version")
    p_analyze.add_argument("--pages", type=int, default=3, choices=range(1, 11),
                           help="Pages to fetch, 1-10 (default: 3)", metavar="PAGES")
    p_analyze.add_argument("--sort", choices=["date", "rating", "votes"], default=None,
                           help="Sort order: date (newest), rating (lowest), votes (most helpful)")
    p_analyze.add_argument("--stats", action="store_true", help="Show rating distribution before analysis")
    p_analyze.add_argument("--list-models", action="store_true", help="List available Ollama models and exit")

    # --- setup ---
    p_setup = sub.add_parser("setup", help="Install agent skill files into your project", parents=[shared])
    p_setup.add_argument("agent", choices=list(AGENTS.keys()),
                         help="Agent to set up: copilot, claude, cursor, windsurf")
    p_setup.add_argument("--force", action="store_true", help="Overwrite existing files")
    p_setup.add_argument("--append", action="store_true", help="Append to existing files instead of overwriting")
    p_setup.add_argument("--global", action="store_true", dest="global",
                         help="Install as a personal skill (~/.copilot/skills/ or ~/.claude/skills/) instead of project-level")

    # --- compare ---
    p_compare = sub.add_parser("compare", help="Compare reviews across multiple apps", parents=[shared])
    p_compare.add_argument("app_ids", nargs="+",
                           help="Two or more app IDs (numeric for Apple, package name for Google)")
    p_compare.add_argument("--stars", type=int, default=None, choices=range(1, 6),
                           help="Max star rating to include", metavar="STARS")
    p_compare.add_argument("--min-stars", type=int, default=None, choices=range(1, 6),
                           help="Min star rating to include", metavar="STARS")
    p_compare.add_argument("--days", type=int, default=None, help="Only reviews from the last N days")
    p_compare.add_argument("--keywords", default=None, help="Comma-separated keywords to filter by")
    p_compare.add_argument("--pages", type=int, default=3, choices=range(1, 11),
                           help="Pages to fetch per app, 1-10 (default: 3)", metavar="PAGES")
    p_compare.add_argument("--sort", choices=["date", "rating", "votes"], default=None,
                           help="Sort order: date (newest), rating (lowest), votes (most helpful)")
    p_compare.add_argument("--format", choices=["text", "json", "csv"], default="text",
                           help="Output format: text (default), json, csv")

    # --- version-diff ---
    p_vdiff = sub.add_parser("version-diff", help="Compare sentiment between app versions", parents=[shared])
    p_vdiff.add_argument("app_id",
                         help="Numeric App Store ID or Google Play package name")
    p_vdiff.add_argument("--old", default=None, help="Old version to compare (auto-detected if omitted)")
    p_vdiff.add_argument("--new", default=None, help="New version to compare (auto-detected if omitted)")
    p_vdiff.add_argument("--stars", type=int, default=None, choices=range(1, 6),
                         help="Max star rating to include", metavar="STARS")
    p_vdiff.add_argument("--min-stars", type=int, default=None, choices=range(1, 6),
                         help="Min star rating to include", metavar="STARS")
    p_vdiff.add_argument("--days", type=int, default=None, help="Only reviews from the last N days")
    p_vdiff.add_argument("--keywords", default=None, help="Comma-separated keywords to filter by")
    p_vdiff.add_argument("--pages", type=int, default=5, choices=range(1, 11),
                         help="Pages to fetch (1-10, default: 5)", metavar="PAGES")
    p_vdiff.add_argument("--format", choices=["text", "json", "csv"], default="text",
                         help="Output format: text (default), json, csv")

    # --- trend ---
    p_trend = sub.add_parser("trend", help="Show rating trend over time", parents=[shared])
    p_trend.add_argument("app_id",
                         help="Numeric App Store ID or Google Play package name")
    p_trend.add_argument("--period", choices=["week", "month"], default="week",
                         help="Group by: week (default) or month")
    p_trend.add_argument("--stars", type=int, default=None, choices=range(1, 6),
                         help="Max star rating to include", metavar="STARS")
    p_trend.add_argument("--min-stars", type=int, default=None, choices=range(1, 6),
                         help="Min star rating to include", metavar="STARS")
    p_trend.add_argument("--days", type=int, default=None, help="Only reviews from the last N days")
    p_trend.add_argument("--keywords", default=None, help="Comma-separated keywords to filter by")
    p_trend.add_argument("--pages", type=int, default=5, choices=range(1, 11),
                         help="Pages to fetch (1-10, default: 5)", metavar="PAGES")
    p_trend.add_argument("--format", choices=["text", "json", "csv"], default="text",
                         help="Output format: text (default), json, csv")

    args = parser.parse_args()

    # Resolve country names/aliases to ISO codes
    args.country = resolve_country(args.country)

    if args.command == "search":
        cmd_search(args)
    elif args.command == "reviews":
        cmd_reviews(args)
    elif args.command == "analyze":
        if args.app_id is None and not args.list_models:
            p_analyze.error("app_id is required (unless using --list-models)")
        cmd_analyze(args)
    elif args.command == "setup":
        cmd_setup(args)
    elif args.command == "compare":
        if len(args.app_ids) < 2:
            p_compare.error("compare requires at least 2 app IDs")
        cmd_compare(args)
    elif args.command == "version-diff":
        cmd_version_diff(args)
    elif args.command == "trend":
        cmd_trend(args)


if __name__ == "__main__":
    main()
