"""Transduck CLI."""

import hashlib
import json
from pathlib import Path

import click
import yaml

import transduck
from transduck.config import load_config
from transduck.scanner import scan_directory
from transduck.storage import TranslationStore
from transduck import backend
from transduck.validation import validate_translation, extract_placeholders
from transduck.plural import get_plural_category, interpolate_vars


def _hash(text: str) -> str:
    return hashlib.sha256(text.encode()).hexdigest()


def _load_dotenv() -> None:
    try:
        from dotenv import load_dotenv, find_dotenv
        load_dotenv(find_dotenv(usecwd=True))
    except ImportError:
        pass


@click.group()
def main():
    """Transduck — AI-native translation tool."""
    _load_dotenv()


@main.command()
def init():
    """Initialize a new transduck project."""
    project_dir = click.prompt("Project directory", type=click.Path())
    project_dir = Path(project_dir)
    project_dir.mkdir(parents=True, exist_ok=True)

    name = click.prompt("Project name")
    context = click.prompt("Project context (describe what you're translating)")
    source_lang = click.prompt("Source language (e.g. EN)").upper()
    targets_raw = click.prompt("Target languages (comma-separated, e.g. DE,ES,IT)")
    target_langs = [t.strip().upper() for t in targets_raw.split(",")]

    click.echo("\nTranslation provider:")
    click.echo("  1. OpenAI (requires API key)")
    click.echo("  2. Claude API (requires API key)")
    click.echo("  3. Claude Code (uses your Claude Code subscription, warming only)")
    provider_choice = click.prompt("Select provider", type=int, default=1)

    config = {
        "project": {"name": name, "context": context},
        "languages": {"source": source_lang, "targets": target_langs},
        "storage": {"path": "./translations.db"},
    }

    if provider_choice == 2:
        config["backend"] = {
            "provider": "claude_api",
            "api_key_env": "ANTHROPIC_API_KEY",
            "model": "claude-haiku-4-5-20251001",
            "timeout_seconds": 10,
            "max_retries": 2,
        }
    elif provider_choice == 3:
        config["backend"] = {
            "provider": "claude_code",
            "token_env": "CLAUDE_CODE_OAUTH_TOKEN",
        }
    else:
        config["backend"] = {
            "provider": "openai",
            "api_key_env": "OPENAI_API_KEY",
            "model": "gpt-4.1-mini",
            "timeout_seconds": 10,
            "max_retries": 2,
        }

    config_path = project_dir / "transduck.yaml"
    with open(config_path, "w") as f:
        yaml.dump(config, f, default_flow_style=False, sort_keys=False)

    db_path = project_dir / "translations.db"
    store = TranslationStore(db_path)
    store.initialize()
    store.close()

    click.echo(f"Created {config_path}")
    click.echo(f"Created {db_path}")

    if provider_choice == 2:
        click.echo("\nAdd to your .env file: ANTHROPIC_API_KEY=your-key-here")
    elif provider_choice == 3:
        click.echo("\nRun 'claude setup-token' to get your OAuth token, then add to your .env file:")
        click.echo("  CLAUDE_CODE_OAUTH_TOKEN=your-token-here")
        click.echo("\nNote: claude_code works for CLI warming only. Your app will run in read-only mode.")
    else:
        click.echo("\nAdd to your .env file: OPENAI_API_KEY=your-key-here")


@main.command()
@click.argument("text")
@click.option("--to", "target_lang", required=True, help="Target language code")
@click.option("--context", "string_context", default=None, help="String context")
@click.option("--config", "config_path", default=None, help="Path to transduck.yaml")
@click.option("--vars", "vars_json", default=None, help="JSON vars for interpolation")
def translate(text, target_lang, string_context, config_path, vars_json):
    """Translate a single string."""
    import time
    start = time.perf_counter()

    cfg = load_config(Path(config_path) if config_path else None)
    target_lang = target_lang.upper()
    vars_dict = json.loads(vars_json) if vars_json else None

    store = TranslationStore(cfg.storage_path)
    store.initialize()

    project_context_hash = _hash(cfg.project_context)
    string_context_hash = _hash(string_context or "")

    cached = store.lookup(
        source_text=text,
        source_lang=cfg.source_lang,
        target_lang=target_lang,
        project_context_hash=project_context_hash,
        string_context_hash=string_context_hash,
    )

    if cached is not None:
        elapsed = (time.perf_counter() - start) * 1000
        click.echo(f"[cached] {interpolate_vars(cached, vars_dict)} ({elapsed:.0f}ms)")
        store.close()
        return

    translated = backend.translate(
        source_text=text,
        source_lang=cfg.source_lang,
        target_lang=target_lang,
        project_context=cfg.project_context,
        string_context=string_context,
        config=cfg,
    )

    elapsed = (time.perf_counter() - start) * 1000

    if not validate_translation(text, translated):
        click.echo(f"[failed] Validation failed: {translated} ({elapsed:.0f}ms)", err=True)
        store.insert(
            source_text=text, source_lang=cfg.source_lang, target_lang=target_lang,
            project_context_hash=project_context_hash, string_context_hash=string_context_hash,
            translated_text=translated, model=cfg.backend_model, status="failed",
            string_context=string_context or "",
        )
    else:
        store.insert(
            source_text=text, source_lang=cfg.source_lang, target_lang=target_lang,
            project_context_hash=project_context_hash, string_context_hash=string_context_hash,
            translated_text=translated, model=cfg.backend_model,
            string_context=string_context or "",
        )
        click.echo(f"[new] {interpolate_vars(translated, vars_dict)} ({elapsed:.0f}ms)")

    store.close()


@main.command("translate-plural")
@click.option("--one", required=True, help="Singular form")
@click.option("--other", required=True, help="Plural form")
@click.option("--count", required=True, type=float, help="Count value")
@click.option("--to", "target_lang", required=True, help="Target language code")
@click.option("--context", "string_context", default=None, help="String context")
@click.option("--config", "config_path", default=None, help="Path to transduck.yaml")
def translate_plural(one, other, count, target_lang, string_context, config_path):
    """Translate a plural string."""
    cfg = load_config(Path(config_path) if config_path else None)
    target_lang = target_lang.upper()

    store = TranslationStore(cfg.storage_path)
    store.initialize()

    project_context_hash = _hash(cfg.project_context)
    string_context_hash = _hash(string_context or "")

    # Auto-include {count} in vars
    vars_dict = {"count": int(count) if count == int(count) else count}

    # Build cache key: two forms joined with null separator
    source_key = one + "\x00" + other

    # Determine needed plural category for target language
    needed_category = get_plural_category(target_lang, count)

    # Check cache
    cached_forms = store.lookup_plural(
        source_text=source_key,
        source_lang=cfg.source_lang,
        target_lang=target_lang,
        project_context_hash=project_context_hash,
        string_context_hash=string_context_hash,
    )

    if needed_category in cached_forms:
        result = interpolate_vars(cached_forms[needed_category], vars_dict)
        click.echo(f"[cached] {result}")
        store.close()
        return

    # Cache miss — call backend
    try:
        forms = backend.translate_plural(
            one=one,
            other=other,
            source_lang=cfg.source_lang,
            target_lang=target_lang,
            project_context=cfg.project_context,
            string_context=string_context,
            config=cfg,
        )

        # Validate each form: collect placeholders from both source forms
        source_placeholders = extract_placeholders(one) | extract_placeholders(other)
        valid_categories = {"zero", "one", "two", "few", "many", "other"}
        all_valid = True

        for cat, translated_text in forms.items():
            if cat not in valid_categories:
                all_valid = False
                break
            if not translated_text or not translated_text.strip():
                all_valid = False
                break
            if source_placeholders:
                translated_phs = extract_placeholders(translated_text)
                if not source_placeholders.issubset(translated_phs):
                    all_valid = False
                    break

        if not all_valid or "other" not in forms:
            # Store as failed, fall back to source
            for cat, translated_text in forms.items():
                store.insert_plural(
                    source_text=source_key, source_lang=cfg.source_lang,
                    target_lang=target_lang, project_context_hash=project_context_hash,
                    string_context_hash=string_context_hash, plural_category=cat,
                    translated_text=translated_text, model=cfg.backend_model, status="failed",
                    string_context=string_context or "",
                )
            # Fallback: use source forms
            source_category = get_plural_category(cfg.source_lang, count)
            fallback = other if source_category == "other" else one
            result = interpolate_vars(fallback, vars_dict)
            click.echo(f"[failed] {result}", err=True)
            store.close()
            return

        # Store all forms
        for cat, translated_text in forms.items():
            store.insert_plural(
                source_text=source_key, source_lang=cfg.source_lang,
                target_lang=target_lang, project_context_hash=project_context_hash,
                string_context_hash=string_context_hash, plural_category=cat,
                translated_text=translated_text, model=cfg.backend_model,
                string_context=string_context or "",
            )

        # Select the right form
        selected = forms.get(needed_category, forms["other"])
        result = interpolate_vars(selected, vars_dict)
        click.echo(f"[new] {result}")

    except Exception:
        # Fallback to source forms
        source_category = get_plural_category(cfg.source_lang, count)
        fallback = other if source_category == "other" else one
        result = interpolate_vars(fallback, vars_dict)
        click.echo(f"[failed] {result}", err=True)

    store.close()


@main.command()
@click.option("--file", "file_path", required=True, type=click.Path(exists=True), help="JSON or text file with strings")
@click.option("--langs", required=True, help="Comma-separated target languages")
@click.option("--config", "config_path", default=None, help="Path to transduck.yaml")
def warm(file_path, langs, config_path):
    """Pre-generate translations from a file."""
    cfg = load_config(Path(config_path) if config_path else None)
    target_langs = [l.strip().upper() for l in langs.split(",")]

    store = TranslationStore(cfg.storage_path)
    store.initialize()

    # Parse input file
    file_path = Path(file_path)
    if file_path.suffix == ".json":
        with open(file_path) as f:
            entries = json.load(f)
        strings = [(e["text"], e.get("context")) for e in entries]
    else:
        with open(file_path) as f:
            strings = [(line.strip(), None) for line in f if line.strip()]

    translated_count = 0
    skipped_count = 0
    failed_count = 0

    project_context_hash = _hash(cfg.project_context)

    for text, ctx in strings:
        string_context_hash = _hash(ctx or "")
        for lang in target_langs:
            cached = store.lookup(
                source_text=text, source_lang=cfg.source_lang, target_lang=lang,
                project_context_hash=project_context_hash, string_context_hash=string_context_hash,
            )
            if cached is not None:
                skipped_count += 1
                continue

            try:
                result = backend.translate(
                    source_text=text, source_lang=cfg.source_lang, target_lang=lang,
                    project_context=cfg.project_context, string_context=ctx,
                    config=cfg,
                )
                if validate_translation(text, result):
                    store.insert(
                        source_text=text, source_lang=cfg.source_lang, target_lang=lang,
                        project_context_hash=project_context_hash, string_context_hash=string_context_hash,
                        translated_text=result, model=cfg.backend_model,
                        string_context=ctx or "",
                    )
                    translated_count += 1
                else:
                    store.insert(
                        source_text=text, source_lang=cfg.source_lang, target_lang=lang,
                        project_context_hash=project_context_hash, string_context_hash=string_context_hash,
                        translated_text=result, model=cfg.backend_model, status="failed",
                        string_context=ctx or "",
                    )
                    failed_count += 1
            except Exception:
                failed_count += 1

    click.echo(f"Translated: {translated_count} | Skipped: {skipped_count} | Failed: {failed_count}")
    store.close()


@main.command()
@click.option("--config", "config_path", default=None, help="Path to transduck.yaml")
def stats(config_path):
    """Show translation database statistics."""
    cfg = load_config(Path(config_path) if config_path else None)
    store = TranslationStore(cfg.storage_path)
    store.initialize()

    st = store.stats()
    click.echo(f"Total translations: {st['total_translations']}")
    click.echo(f"Failed translations: {st['total_failed']}")
    if st["by_language"]:
        click.echo("By language:")
        for lang, count in sorted(st["by_language"].items()):
            click.echo(f"  {lang}: {count}")
    store.close()


@main.command()
@click.option("--lang", default=None, help="Only clear translations for this language")
@click.option("--failed-only", is_flag=True, help="Only clear failed translations")
@click.option("--config", "config_path", default=None, help="Path to transduck.yaml")
def clear(lang, failed_only, config_path):
    """Clear cached translations from the database."""
    cfg = load_config(Path(config_path) if config_path else None)
    store = TranslationStore(cfg.storage_path)
    store.initialize()

    target_lang = lang.upper() if lang else None

    # Count what will be deleted
    entry_count = store.count(target_lang=target_lang, failed_only=failed_only)

    if target_lang and failed_only:
        desc = f"{entry_count} failed translation(s) for {target_lang}"
    elif target_lang:
        desc = f"{entry_count} translation(s) for {target_lang}"
    elif failed_only:
        desc = f"{entry_count} failed translation(s) across all languages"
    else:
        desc = f"{entry_count} translation(s) across all languages"

    if entry_count == 0:
        click.echo("Nothing to clear.")
        store.close()
        return

    click.echo(f"This will delete {desc}.")
    confirm = click.prompt('Type "Yes" to confirm', default="")
    if confirm != "Yes":
        click.echo("Cancelled.")
        store.close()
        return

    # Delete
    store.delete(target_lang=target_lang, failed_only=failed_only)

    click.echo(f"Deleted {desc}.")
    store.close()


@main.command()
@click.option("--dir", "dirs", multiple=True, type=click.Path(exists=True), help="Directories to scan (repeatable)")
@click.option("--warm", "do_warm", is_flag=True, help="Translate all found strings")
@click.option("--langs", default=None, help="Comma-separated target languages (for --warm)")
@click.option("--output", "output_path", default=None, type=click.Path(), help="Write found strings to JSON")
@click.option("--config", "config_path", default=None, help="Path to transduck.yaml")
def scan(dirs, do_warm, langs, output_path, config_path):
    """Scan source code for translatable strings."""
    cfg = load_config(Path(config_path) if config_path else None)

    scan_dirs = [Path(d) for d in dirs] if dirs else [Path.cwd()]
    click.echo("Scanning...")
    entries = scan_directory(scan_dirs)

    regular = [e for e in entries if not e.get("plural")]
    plurals = [e for e in entries if e.get("plural")]

    # Count scanned files
    all_files = set()
    for e in entries:
        all_files.update(e.get("files", []))
    file_count = len(all_files)

    click.echo(f"Scanned files with matches: {file_count}")
    click.echo(f"Found {len(entries)} strings ({len(regular)} regular, {len(plurals)} plural)")
    click.echo()

    for e in entries:
        locations = ", ".join(e.get("files", []))
        if e.get("plural"):
            click.echo(f'  ait_plural("{e["one"]}", "{e["other"]}")  {locations}')
        else:
            ctx = f', context="{e["context"]}"' if e.get("context") else ""
            click.echo(f'  ait("{e["text"]}"{ctx})  {locations}')

    # Output to JSON
    if output_path:
        # Remove 'files' for warm-compatible output
        output_entries = []
        for e in entries:
            out = {k: v for k, v in e.items() if k != "files"}
            output_entries.append(out)
        with open(output_path, "w") as f:
            json.dump(output_entries, f, indent=2, ensure_ascii=False)
        click.echo(f"\nWrote {len(entries)} entries to {output_path}")

    # Warm
    if do_warm:
        if not entries:
            click.echo("\nNothing to warm — no translatable strings found.")
        else:
            target_langs = [l.strip().upper() for l in langs.split(",")] if langs else cfg.target_langs
            store = TranslationStore(cfg.storage_path)
            store.initialize()
            project_context_hash = _hash(cfg.project_context)

            translated_count = 0
            skipped_count = 0
            failed_count = 0

            use_batch = backend.supports_batch(cfg)

            if use_batch:
                # --- Batch mode (claude_code): one session per language ---
                regular = [e for e in entries if not e.get("plural")]
                plurals = [e for e in entries if e.get("plural")]

                for lang in target_langs:
                    # Filter uncached regular strings
                    to_translate = []
                    for entry in regular:
                        string_context_hash = _hash(entry.get("context") or "")
                        cached = store.lookup(
                            source_text=entry["text"], source_lang=cfg.source_lang, target_lang=lang,
                            project_context_hash=project_context_hash, string_context_hash=string_context_hash,
                        )
                        if cached is not None:
                            skipped_count += 1
                            click.echo(f"  {lang} skipped (cached): {entry['text'][:40]}")
                        else:
                            to_translate.append(entry)

                    if to_translate:
                        click.echo(f"  {lang} translating {len(to_translate)} strings in batch...")
                        try:
                            results = backend.translate_batch(
                                [{"text": e["text"], "context": e.get("context")} for e in to_translate],
                                source_lang=cfg.source_lang, target_lang=lang,
                                project_context=cfg.project_context, config=cfg,
                            )
                            for entry in to_translate:
                                translated_text = results.get(entry["text"])
                                if translated_text and validate_translation(entry["text"], translated_text):
                                    string_context_hash = _hash(entry.get("context") or "")
                                    store.insert(
                                        source_text=entry["text"], source_lang=cfg.source_lang, target_lang=lang,
                                        project_context_hash=project_context_hash, string_context_hash=string_context_hash,
                                        translated_text=translated_text, model="claude_code",
                                        string_context=entry.get("context") or "",
                                    )
                                    translated_count += 1
                                    click.echo(f"  {lang} translated: {entry['text'][:40]}")
                                else:
                                    failed_count += 1
                                    click.echo(f"  {lang} failed: {entry['text'][:40]}")
                        except Exception as e:
                            click.echo(f"  {lang} batch failed: {e}", err=True)
                            failed_count += len(to_translate)

                    # Filter uncached plural strings
                    to_translate_plural = []
                    for entry in plurals:
                        source_key = entry["one"] + "\x00" + entry["other"]
                        string_context_hash = _hash(entry.get("context") or "")
                        cached = store.lookup_plural(
                            source_text=source_key, source_lang=cfg.source_lang, target_lang=lang,
                            project_context_hash=project_context_hash, string_context_hash=string_context_hash,
                        )
                        if cached:
                            skipped_count += 1
                            click.echo(f"  {lang} skipped (cached): {entry['one'][:40]}")
                        else:
                            to_translate_plural.append(entry)

                    if to_translate_plural:
                        click.echo(f"  {lang} translating {len(to_translate_plural)} plural strings in batch...")
                        try:
                            results_list = backend.translate_plural_batch(
                                [{"one": e["one"], "other": e["other"], "context": e.get("context")} for e in to_translate_plural],
                                source_lang=cfg.source_lang, target_lang=lang,
                                project_context=cfg.project_context, config=cfg,
                            )
                            for entry, forms in zip(to_translate_plural, results_list):
                                source_key = entry["one"] + "\x00" + entry["other"]
                                string_context_hash = _hash(entry.get("context") or "")
                                for cat, text in forms.items():
                                    store.insert_plural(
                                        source_text=source_key, source_lang=cfg.source_lang, target_lang=lang,
                                        project_context_hash=project_context_hash, string_context_hash=string_context_hash,
                                        plural_category=cat, translated_text=text, model="claude_code",
                                        string_context=entry.get("context") or "",
                                    )
                                translated_count += 1
                                click.echo(f"  {lang} translated: {entry['one'][:40]}")
                        except Exception as e:
                            click.echo(f"  {lang} plural batch failed: {e}", err=True)
                            failed_count += len(to_translate_plural)

            else:
                # --- Per-string mode (openai, claude_api) ---
                total = len(entries) * len(target_langs)
                done = 0

                for entry in entries:
                    if entry.get("plural"):
                        source_key = entry["one"] + "\x00" + entry["other"]
                        string_context_hash = _hash(entry.get("context") or "")
                        for lang in target_langs:
                            done += 1
                            cached = store.lookup_plural(
                                source_text=source_key, source_lang=cfg.source_lang, target_lang=lang,
                                project_context_hash=project_context_hash, string_context_hash=string_context_hash,
                            )
                            if cached:
                                skipped_count += 1
                                click.echo(f"  [{done}/{total}] {lang} skipped (cached): {entry['one'][:40]}")
                                continue
                            try:
                                forms = backend.translate_plural(
                                    one=entry["one"], other=entry["other"],
                                    source_lang=cfg.source_lang, target_lang=lang,
                                    project_context=cfg.project_context, string_context=entry.get("context"),
                                    config=cfg,
                                )
                                for cat, text in forms.items():
                                    store.insert_plural(
                                        source_text=source_key, source_lang=cfg.source_lang, target_lang=lang,
                                        project_context_hash=project_context_hash, string_context_hash=string_context_hash,
                                        plural_category=cat, translated_text=text, model=cfg.backend_model,
                                        string_context=entry.get("context") or "",
                                    )
                                translated_count += 1
                                click.echo(f"  [{done}/{total}] {lang} translated: {entry['one'][:40]}")
                            except Exception:
                                failed_count += 1
                                click.echo(f"  [{done}/{total}] {lang} failed: {entry['one'][:40]}")
                    else:
                        string_context_hash = _hash(entry.get("context") or "")
                        for lang in target_langs:
                            done += 1
                            cached = store.lookup(
                                source_text=entry["text"], source_lang=cfg.source_lang, target_lang=lang,
                                project_context_hash=project_context_hash, string_context_hash=string_context_hash,
                            )
                            if cached is not None:
                                skipped_count += 1
                                click.echo(f"  [{done}/{total}] {lang} skipped (cached): {entry['text'][:40]}")
                                continue
                            try:
                                result = backend.translate(
                                    source_text=entry["text"], source_lang=cfg.source_lang, target_lang=lang,
                                    project_context=cfg.project_context, string_context=entry.get("context"),
                                    config=cfg,
                                )
                                if validate_translation(entry["text"], result):
                                    store.insert(
                                        source_text=entry["text"], source_lang=cfg.source_lang, target_lang=lang,
                                        project_context_hash=project_context_hash, string_context_hash=string_context_hash,
                                        translated_text=result, model=cfg.backend_model,
                                        string_context=entry.get("context") or "",
                                    )
                                    translated_count += 1
                                    click.echo(f"  [{done}/{total}] {lang} translated: {entry['text'][:40]}")
                                else:
                                    failed_count += 1
                                    click.echo(f"  [{done}/{total}] {lang} failed: {entry['text'][:40]}")
                            except Exception:
                                failed_count += 1
                                click.echo(f"  [{done}/{total}] {lang} failed: {entry['text'][:40]}")

            click.echo(f"\nTranslated: {translated_count} | Skipped: {skipped_count} | Failed: {failed_count}")
            store.close()

    if not do_warm and not output_path:
        click.echo(f"\nRun 'transduck scan --warm --langs DE,ES' to translate all strings.")
