"""Sync command registration."""

from __future__ import annotations

import json
import sys
from pathlib import Path

import click

from g_gremlin.safety import SafetyContext
from g_gremlin.cli import (
    _check_license,
    _compute_plan_hash,
    _emit_json_payload,
    _get_apps_script,
    _get_build_impact,
    _get_print_impact_box,
    _get_script_resolution_error,
    _get_snapshot,
    _get_update_stats,
    _handle_restore_on_error,
    _should_emit_json,
    cli,
)


@cli.command()
@click.argument("spreadsheet_id", required=False)
@click.option(
    "--sheet-id",
    "sheet_id_opt",
    help="Spreadsheet ID (alternative to positional argument).",
)
@click.option(
    "--config",
    "config_path",
    required=True,
    type=click.Path(exists=True, dir_okay=False, path_type=Path),
    help="Path to sync config file (YAML or JSON).",
)
@click.option(
    "--apply",
    is_flag=True,
    help="Actually execute the sync (default is dry-run).",
)
@click.option(
    "--only",
    "only_tab",
    help="Only sync this specific tab from the config.",
)
@click.option(
    "--validate-live",
    is_flag=True,
    help="Make one API call to validate sheet references exist.",
)
@click.option(
    "--strict-refs",
    is_flag=True,
    help="Fail if any sheet references are missing (instead of warning).",
)
@click.option(
    "--plan-out",
    "plan_output",
    type=click.Path(dir_okay=False, path_type=Path),
    help="Output sync plan to JSON file.",
)
@click.option(
    "--push-scripts",
    is_flag=True,
    help="After applying, push Apps Script code from --scripts-dir.",
)
@click.option(
    "--scripts-dir",
    default=Path("scripts"),
    show_default=True,
    type=click.Path(file_okay=False, path_type=Path),
    help="Directory containing Apps Script files for --push-scripts.",
)
@click.option(
    "--script-id",
    "script_id_opt",
    help="Apps Script project ID override for --push-scripts.",
)
@click.option(
    "--resume",
    is_flag=True,
    help="Enable checkpointed streaming writes so sync can be resumed if interrupted.",
)
@click.option(
    "--format",
    "output_format",
    type=click.Choice(["text", "json"], case_sensitive=False),
    default="text",
    show_default=True,
    help="Output format for summary (text|json).",
)
@click.option(
    "--json",
    "json_flag",
    is_flag=True,
    help="Shortcut for --format json.",
)
@click.option(
    "--output",
    "output_path",
    type=click.Path(dir_okay=False, path_type=Path),
    help="Write JSON summary to a file (use - for stdout).",
)
@click.option(
    "--json-summary",
    "json_summary",
    is_flag=True,
    help="Emit AgenticResult-style JSON summary.",
)
@click.option(
    "--backup/--no-backup",
    default=True,
    show_default=True,
    help="Create snapshot before applying sync (default on for safety).",
)
@click.option(
    "--auto-rollback",
    is_flag=True,
    help="Automatically restore from backup on error (for CI/agents).",
)
def sync(
    spreadsheet_id: str | None,
    sheet_id_opt: str | None,
    config_path: Path,
    apply: bool,
    only_tab: str | None,
    validate_live: bool,
    strict_refs: bool,
    plan_output: Path | None,
    push_scripts: bool,
    scripts_dir: Path,
    script_id_opt: str | None,
    resume: bool,
    output_format: str,
    json_flag: bool,
    output_path: Path | None,
    json_summary: bool,
    backup: bool,
    auto_rollback: bool,
) -> None:
    """
    Sync multiple sheet tabs from CSV files using YAML configuration.

    IMPORTANT: Defaults to dry-run mode for safety. Use --apply to execute changes.
    Schema warnings show if CSV columns differ from current sheet headers.

    \b
    Common Usage:
      # Preview changes (dry-run, no mutations)
      g-gremlin sync SHEET_ID --config g_gremlin.yaml

      # Apply changes after reviewing dry-run output
      g-gremlin sync SHEET_ID --config g_gremlin.yaml --apply

      # Sync only specific tab (faster iteration)
      g-gremlin sync SHEET_ID --config g_gremlin.yaml --only "Core_Filters" --apply

      # Resume interrupted sync
      g-gremlin sync SHEET_ID --config g_gremlin.yaml --apply --resume

    \b
    Workflow:
      1. Run dry-run to preview changes and check for schema warnings
      2. Review per-tab row counts and schema diff warnings
      3. Apply if safe with --apply flag

    \b
    For AI Agents:
      - Dry-run by default (--apply required to mutate)
      - Returns exit code 0 on success, 1 on failure
      - Use --json-summary for structured AgenticResult output
      - Schema warnings appear in dry-run output and in warnings array

    Note: This is a licensed command. Requires a valid FoundryOps trial or subscription.
    Run 'g-gremlin auth login' to authenticate.
    """
    # Licensed command - check for valid license
    _check_license()

    from g_gremlin.config_schema import load_config, ConfigValidationError
    from g_gremlin.sync import build_sync_plan, execute_sync, format_plan_output, validate_live_refs

    safety_ctx = SafetyContext(command="g-gremlin sync", remote_mutation=True)
    if apply:
        safety_ctx.enforce_apply(apply)
    emit_json = _should_emit_json(output_format, json_flag, json_summary) or output_path is not None

    # Resolve sheet_id from argument or option
    sheet_id = spreadsheet_id or sheet_id_opt

    # Load config
    try:
        config = load_config(config_path, sheet_id_override=sheet_id)
    except FileNotFoundError as e:
        click.echo(f"Error: {e}", err=True)
        sys.exit(1)
    except ConfigValidationError as e:
        click.echo(f"Config error: {e}", err=True)
        sys.exit(1)

    if push_scripts and not apply:
        click.echo("--push-scripts requires --apply (no changes made).", err=True)
        sys.exit(1)

    # Use sheet_id from config if not provided
    if not config.sheet_id:
        click.echo("Error: sheet_id is required (via argument, --sheet-id, or in config)", err=True)
        sys.exit(1)

    # Determine whether to validate sheet references live
    validate_live = validate_live or config.settings.validate_live_refs

    # Build plan with schema checking enabled
    plan = build_sync_plan(config, only_tab=only_tab, check_schemas=True)

    # Validate live refs if requested (via CLI flag or config setting)
    if validate_live or config.settings.validate_live_refs:
        try:
            found, missing = validate_live_refs(config, plan)
            if missing:
                msg = f"Missing sheets in spreadsheet: {', '.join(missing)}"
                if strict_refs:
                    click.echo(f"Error: {msg}", err=True)
                    sys.exit(1)
                else:
                    plan.warnings.append(msg)
        except Exception as e:
            click.echo(f"Error validating sheet refs: {e}", err=True)
            if strict_refs:
                sys.exit(1)

    # Check for strict-refs failures from config-only validation
    if strict_refs and plan.missing_refs:
        click.echo(
            f"Error: Formulas reference sheets not in config: {', '.join(sorted(plan.missing_refs))}",
            err=True,
        )
        sys.exit(1)

    plan_hash = _compute_plan_hash(
        config_path,
        plan,
        extra_fields={
            "apply": apply,
            "resume": resume,
            "only_tab": only_tab or "",
            "strict_refs": strict_refs,
            "validate_live": validate_live,
        },
    )

    # Check max cells
    if plan.total_cells > config.settings.max_cells:
        click.echo(
            f"Error: Would write {plan.total_cells:,} cells, exceeds max_cells ({config.settings.max_cells:,})",
            err=True,
        )
        sys.exit(1)

    # Output plan to file if requested
    if plan_output:
        plan_dict = plan.to_dict()
        plan_dict["plan_hash"] = plan_hash
        with plan_output.open("w", encoding="utf-8") as f:
            json.dump(plan_dict, f, indent=2)
        click.echo(f"Plan written to: {plan_output}")

    # Dry run output
    if not apply:
        click.echo(format_plan_output(plan))
        click.echo(f"Plan hash: {plan_hash}")
        if emit_json:
            agentic = safety_ctx.build_result(
                status="success",
                dry_run=True,
                result={
                    "tabs_planned": len(plan.tabs),
                    "warnings": plan.warnings,
                    "plan_hash": plan_hash,
                    "run_id": safety_ctx.correlation_id,
                },
            )
            _emit_json_payload(json.dumps(agentic.to_dict(), indent=2), output_path)
        return

    # Create snapshot before sync
    snapshot_result = None
    if backup:
        snap_mod = _get_snapshot()
        from datetime import datetime
        timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
        snapshot_name = f"pre-sync-{timestamp}"
        tabs_to_backup = [t.tab_name for t in plan.tabs]
        try:
            cmd_metadata = snap_mod.collect_command_metadata()
            cmd_metadata["plan_hash"] = plan_hash
            cmd_metadata["run_id"] = safety_ctx.correlation_id
            snapshot_result = snap_mod.create_snapshot(
                spreadsheet_id=config.sheet_id,
                tabs=tabs_to_backup,
                name=snapshot_name,
                metadata=cmd_metadata,
            )
            click.echo(f"Created backup snapshot: {snapshot_name}")
        except Exception as e:
            click.echo(f"Warning: Failed to create backup snapshot: {e}", err=True)

    # Execute sync
    click.echo(f"Syncing {len(plan.tabs)} tabs to {config.sheet_id}...")
    try:
        result = execute_sync(config, plan, dry_run=False, resume=resume)
    except Exception as e:
        _handle_restore_on_error(e, snapshot_result, auto_rollback, emit_json)
        raise

    script_push_failed = False
    if apply and push_scripts:
        try:
            script_id = _get_apps_script().resolve_script_id(
                config.sheet_id,
                explicit_script_id=script_id_opt,
                create_if_missing=True,
            )
            file_count = _get_apps_script().push_script(script_id, scripts_dir)
            click.echo(
                f"Pushed {file_count} Apps Script file(s) from {scripts_dir} to scriptId={script_id}."
            )
        except _get_script_resolution_error() as exc:
            result.errors.append(str(exc))
            script_push_failed = True
        except Exception as exc:
            result.errors.append(f"Apps Script push failed: {exc}")
            script_push_failed = True

    if result.errors:
        for error in result.errors:
            click.echo(f"Error: {error}", err=True)

    overall_success = result.success and not script_push_failed
    if overall_success:
        click.echo(f"Sync complete: {result.tabs_written} tabs, {result.total_rows} rows written.")

        # Show per-tab details if available
        if result.tab_results:
            click.echo("")
            for tab_result in result.tab_results:
                delta = tab_result.rows_written - tab_result.existing_rows_before
                if tab_result.existing_rows_before == 0:
                    click.echo(f"  ✓ {tab_result.tab_name}: {tab_result.rows_written} rows written (was 0)")
                elif delta > 0:
                    click.echo(f"  ✓ {tab_result.tab_name}: {tab_result.rows_written} rows written (was {tab_result.existing_rows_before}, +{delta})")
                elif delta < 0:
                    click.echo(f"  ✓ {tab_result.tab_name}: {tab_result.rows_written} rows written (was {tab_result.existing_rows_before}, {delta})")
                else:
                    click.echo(f"  ✓ {tab_result.tab_name}: {tab_result.rows_written} rows written (unchanged)")

        impact = _get_build_impact()(
            action="Config sync",
            stats={
                "tabs_written": result.tabs_written,
                "rows_written": result.total_rows,
            },
            scenario="sync_complete",
            trophy="🔄",
            rows=result.total_rows,
        )
        _get_print_impact_box()(impact)
        _get_update_stats()("sync", impact["stats"])
        click.echo(f"Plan hash: {plan_hash}")
        click.echo(f"Run ID: {safety_ctx.correlation_id}")
    else:
        click.echo("Sync failed with errors.", err=True)
        click.echo(f"Plan hash: {plan_hash}")
        click.echo(f"Run ID: {safety_ctx.correlation_id}")
        sys.exit(1)

    if emit_json:
        status = "success" if overall_success else "fail"
        result_data = {
            "tabs_written": result.tabs_written,
            "rows_written": result.total_rows,
            "warnings": plan.warnings,
            "errors": result.errors,
            "resume": resume,
            "plan_hash": plan_hash,
            "run_id": safety_ctx.correlation_id,
        }
        # Add snapshot info if backup was created
        if snapshot_result and snapshot_result.success:
            result_data["snapshot_name"] = snapshot_result.snapshot_path.stem.rsplit("_", 2)[0]
            result_data["snapshot_path"] = str(snapshot_result.snapshot_path)
            result_data["restore_command"] = f"g-gremlin snapshot restore --name {result_data['snapshot_name']}"
        agentic = safety_ctx.build_result(
            status=status,
            dry_run=not apply,
            result=result_data,
        )
        _emit_json_payload(json.dumps(agentic.to_dict(), indent=2), output_path)
