"""Transduck CLI."""

import hashlib
import json
import os
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()


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


@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(",")]

    config = {
        "project": {"name": name, "context": context},
        "languages": {"source": source_lang, "targets": target_langs},
        "storage": {"path": "./translations.duckdb"},
        "backend": {
            "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.duckdb"
    store = TranslationStore(db_path)
    store.initialize()
    store.close()

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


@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."""
    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:
        click.echo(f"[cached] {interpolate_vars(cached, vars_dict)}")
        store.close()
        return

    api_key = os.environ.get(cfg.api_key_env)
    translated = backend.translate(
        source_text=text,
        source_lang=cfg.source_lang,
        target_lang=target_lang,
        project_context=cfg.project_context,
        string_context=string_context,
        api_key=api_key,
        model=cfg.backend_model,
        timeout=cfg.backend_timeout,
        max_retries=cfg.backend_max_retries,
    )

    if not validate_translation(text, translated):
        click.echo(f"[failed] Validation failed: {translated}", 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",
        )
    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,
        )
        click.echo(f"[new] {interpolate_vars(translated, vars_dict)}")

    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:
        api_key = os.environ.get(cfg.api_key_env)
        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,
            api_key=api_key,
            model=cfg.backend_model,
            timeout=cfg.backend_timeout,
            max_retries=cfg.backend_max_retries,
        )

        # 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",
                )
            # 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,
            )

        # 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()]

    api_key = os.environ.get(cfg.api_key_env)
    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,
                    api_key=api_key, model=cfg.backend_model,
                    timeout=cfg.backend_timeout, max_retries=cfg.backend_max_retries,
                )
                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,
                    )
                    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",
                    )
                    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("--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()]
    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:
        target_langs = [l.strip().upper() for l in langs.split(",")] if langs else cfg.target_langs
        store = TranslationStore(cfg.storage_path)
        store.initialize()
        api_key = os.environ.get(cfg.api_key_env)
        project_context_hash = _hash(cfg.project_context)

        translated_count = 0
        skipped_count = 0
        failed_count = 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:
                    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
                        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"),
                            api_key=api_key, model=cfg.backend_model,
                            timeout=cfg.backend_timeout, max_retries=cfg.backend_max_retries,
                        )
                        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,
                            )
                        translated_count += 1
                    except Exception:
                        failed_count += 1
            else:
                string_context_hash = _hash(entry.get("context") or "")
                for lang in target_langs:
                    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
                        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"),
                            api_key=api_key, model=cfg.backend_model,
                            timeout=cfg.backend_timeout, max_retries=cfg.backend_max_retries,
                        )
                        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,
                            )
                            translated_count += 1
                        else:
                            failed_count += 1
                    except Exception:
                        failed_count += 1

        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.")
