"""Command-line interface for opendeviationbar-py cache management.

This module provides CLI commands for managing the ClickHouse cache,
including status checks, population, and clearing operations.

Usage
-----
After installation, the CLI is available as `opendeviationbar`:

    $ opendeviationbar status BTCUSDT
    $ opendeviationbar populate BTCUSDT --start 2024-01-01 --end 2024-06-30
    $ opendeviationbar clear BTCUSDT --confirm

Or run as a module:

    $ python -m opendeviationbar.cli status BTCUSDT
"""

from __future__ import annotations

import sys
from datetime import UTC, datetime

import click

from opendeviationbar.logging import setup_service_logging


@click.group()
@click.option("-v", "--verbose", is_flag=True, help="Enable verbose output")
@click.version_option(package_name="opendeviationbar")
def cli(verbose: bool) -> None:
    """opendeviationbar-py cache management CLI.

    Manage ClickHouse cache for computed open deviation bars.
    """
    setup_service_logging("opendeviationbar-cli", verbose=verbose)


@cli.command()
@click.argument("symbol")
@click.option(
    "--threshold",
    "-t",
    type=int,
    default=250,
    help="Threshold in dbps (default: 250)",
)
def status(symbol: str, threshold: int) -> None:
    """Show cache status for a symbol.

    Example: opendeviationbar status BTCUSDT
    """
    from .symbol_registry import validate_symbol_registered

    validate_symbol_registered(symbol, operation="cli_status")

    try:
        from .clickhouse import OpenDeviationBarCache
    except ImportError as e:
        click.echo(f"Error: ClickHouse support not available: {e}", err=True)
        sys.exit(1)

    try:
        with OpenDeviationBarCache() as cache:
            # Get bar count
            count = cache.count_bars(symbol, threshold)

            # Get timestamp range
            oldest = cache.get_oldest_bar_timestamp(symbol, threshold)
            newest = cache.get_newest_bar_timestamp(symbol, threshold)

            click.echo(f"Symbol: {symbol}")
            click.echo(f"Threshold: {threshold} dbps")
            click.echo(f"Cached bars: {count:,}")

            if oldest and newest:
                oldest_dt = datetime.fromtimestamp(oldest / 1000, tz=UTC)
                newest_dt = datetime.fromtimestamp(newest / 1000, tz=UTC)
                click.echo(f"Date range: {oldest_dt.date()} to {newest_dt.date()}")
            else:
                click.echo("Date range: N/A (no cached bars)")

    except ConnectionError as e:
        click.echo(f"Error: Cannot connect to ClickHouse: {e}", err=True)
        sys.exit(1)


@cli.command()
@click.argument("symbol")
@click.option("--start", "-s", required=True, help="Start date (YYYY-MM-DD)")
@click.option("--end", "-e", required=True, help="End date (YYYY-MM-DD)")
@click.option(
    "--threshold",
    "-t",
    type=int,
    default=250,
    help="Threshold in dbps (default: 250)",
)
@click.option(
    "--notify/--no-notify",
    default=True,
    help="Send Telegram notifications (default: on)",
)
@click.option(
    "--ouroboros-mode",
    type=click.Choice(["day", "aion"]),
    default=None,
    help="Ouroboros reset mode (default: aion, from config)",
)
def populate(
    symbol: str,
    start: str,
    end: str,
    threshold: int,
    notify: bool,
    ouroboros_mode: str | None,
) -> None:
    """Populate cache for a date range.

    Fetches tick data and computes open deviation bars, storing results in ClickHouse.

    Example: opendeviationbar populate BTCUSDT --start 2024-01-01 --end 2024-06-30
    """
    from .symbol_registry import validate_symbol_registered

    validate_symbol_registered(symbol, operation="cli_populate")

    # Validate dates
    try:
        datetime.strptime(start, "%Y-%m-%d")  # noqa: DTZ007
        datetime.strptime(end, "%Y-%m-%d")  # noqa: DTZ007
    except ValueError:
        click.echo("Error: Invalid date format. Use YYYY-MM-DD", err=True)
        sys.exit(1)

    # Issue #134: Resolve ouroboros mode — CLI flag > config default
    from opendeviationbar.ouroboros import get_operational_ouroboros_mode

    resolved_mode = ouroboros_mode or get_operational_ouroboros_mode()

    # Enable Telegram notifications if requested
    if notify:
        try:
            from .notify.telegram import enable_telegram_notifications

            enable_telegram_notifications()
        except ImportError:
            click.echo("Warning: Telegram notifications not available", err=True)

    click.echo(f"Populating cache for {symbol} from {start} to {end}...")
    click.echo(f"Threshold: {threshold} dbps | Ouroboros: {resolved_mode}")

    try:
        from . import get_open_deviation_bars

        df = get_open_deviation_bars(
            symbol,
            start,
            end,
            threshold_decimal_bps=threshold,
            use_cache=True,
            fetch_if_missing=True,
        )

        click.echo(f"Computed {len(df):,} bars")
        click.echo("Bars should now be cached in ClickHouse")

    except (ValueError, RuntimeError) as e:
        click.echo(f"Error: {e}", err=True)
        sys.exit(1)


@cli.command()
@click.argument("symbol")
@click.option(
    "--threshold",
    "-t",
    type=int,
    default=None,
    help="Threshold in dbps (default: all thresholds)",
)
@click.option("--confirm", is_flag=True, help="Confirm deletion (required)")
def clear(symbol: str, threshold: int | None, confirm: bool) -> None:
    """Clear cache for a symbol.

    Requires --confirm flag to prevent accidental deletion.

    Example: opendeviationbar clear BTCUSDT --confirm
    """
    from .symbol_registry import validate_symbol_registered

    validate_symbol_registered(symbol, operation="cli_clear")

    if not confirm:
        click.echo("Error: Add --confirm flag to delete cache data", err=True)
        click.echo(f"  opendeviationbar clear {symbol} --confirm")
        sys.exit(1)

    try:
        from .clickhouse import OpenDeviationBarCache
    except ImportError as e:
        click.echo(f"Error: ClickHouse support not available: {e}", err=True)
        sys.exit(1)

    try:
        with OpenDeviationBarCache() as cache:
            if threshold:
                # Clear specific threshold
                click.echo(f"Clearing cache for {symbol} @ {threshold} dbps...")

                # Get count before clearing
                count = cache.count_bars(symbol, threshold)
                if count == 0:
                    click.echo("No cached bars found")
                    return

                # Delete using timestamp range (all time)
                # Issue #126: resolve ouroboros_mode from config
                from opendeviationbar.ouroboros import get_operational_ouroboros_mode

                cache.invalidate_open_deviation_bars_by_range(
                    symbol=symbol,
                    threshold_decimal_bps=threshold,
                    start_timestamp_ms=0,
                    end_timestamp_ms=int(datetime.now(tz=UTC).timestamp() * 1000),
                    ouroboros_mode=get_operational_ouroboros_mode(),
                )
                click.echo(f"Cleared {count:,} bars (deletion is async)")
            else:
                click.echo(f"Clearing all cache for {symbol}...")
                # Would need to query all thresholds and clear each
                # For now, just show a message
                click.echo(
                    "Note: Clearing all thresholds not yet implemented. "
                    "Specify --threshold to clear a specific threshold."
                )

    except ConnectionError as e:
        click.echo(f"Error: Cannot connect to ClickHouse: {e}", err=True)
        sys.exit(1)


@cli.command()
def list_symbols() -> None:
    """List all cached symbols and their thresholds."""
    try:
        from .clickhouse import OpenDeviationBarCache
    except ImportError as e:
        click.echo(f"Error: ClickHouse support not available: {e}", err=True)
        sys.exit(1)

    try:
        with OpenDeviationBarCache() as cache:
            # Query distinct symbol/threshold combinations
            query = """
                SELECT symbol, threshold_decimal_bps, count(*) as bar_count
                FROM opendeviationbar_cache.open_deviation_bars FINAL
                GROUP BY symbol, threshold_decimal_bps
                ORDER BY symbol, threshold_decimal_bps
            """
            result = cache.client.query(query)

            if not result.result_rows:
                click.echo("No cached data found")
                return

            click.echo("Cached symbols:")
            click.echo("-" * 50)
            click.echo(f"{'Symbol':<12} {'Threshold':<12} {'Bars':>12}")
            click.echo("-" * 50)

            for row in result.result_rows:
                symbol, threshold, count = row
                click.echo(f"{symbol:<12} {threshold:<12} {count:>12,}")

    except ConnectionError as e:
        click.echo(f"Error: Cannot connect to ClickHouse: {e}", err=True)
        sys.exit(1)


@cli.command()
def test_telegram() -> None:
    """Send a test Telegram notification."""
    try:
        from .notify.telegram import is_configured, send_telegram
    except ImportError:
        click.echo("Error: Telegram module not available", err=True)
        sys.exit(1)

    if not is_configured():
        click.echo(
            "Error: Telegram not configured. "
            "Set OPENDEVIATIONBAR_TELEGRAM_TOKEN environment variable.",
            err=True,
        )
        sys.exit(1)

    success = send_telegram(
        "<b>opendeviationbar-py CLI Test</b>\n\n"
        "This is a test notification from the opendeviationbar CLI.\n"
        "If you see this, Telegram notifications are working correctly."
    )

    if success:
        click.echo("Test notification sent successfully")
    else:
        click.echo("Failed to send test notification", err=True)
        sys.exit(1)


# ---------------------------------------------------------------------------
# config show — Issue #110 Phase 5
# ---------------------------------------------------------------------------


@cli.group()
def config() -> None:
    """View resolved configuration."""


@config.command()
@click.option(
    "--section",
    type=click.Choice(
        ["population", "monitoring", "clickhouse", "sidecar", "algorithm", "streaming"],
    ),
    default=None,
    help="Show only one config section",
)
@click.option("--json", "as_json", is_flag=True, help="Machine-readable JSON output")
def show(section: str | None, as_json: bool) -> None:
    """Print resolved configuration (all sources merged).

    \b
    Examples:
        opendeviationbar config show
        opendeviationbar config show --section population
        opendeviationbar config show --json
    """
    import json as json_mod

    from opendeviationbar.config import Settings

    s = Settings.get()

    sections: dict[str, dict] = {}
    if section is None or section == "population":
        sections["population"] = s.population.model_dump()
    if section is None or section == "monitoring":
        sections["monitoring"] = s.monitoring.model_dump()
    if section is None or section == "clickhouse":
        sections["clickhouse"] = s.clickhouse.model_dump()
    if section is None or section == "sidecar":
        sections["sidecar"] = s.sidecar.model_dump()
    if section is None or section == "algorithm":
        sections["algorithm"] = s.algorithm.model_dump()
    if section is None or section == "streaming":
        sections["streaming"] = s.streaming.model_dump()

    if as_json:
        click.echo(json_mod.dumps(sections, indent=2, default=str))
        return

    for sec_name, fields in sections.items():
        click.echo(f"\n[{sec_name}]")
        for key, value in fields.items():
            click.echo(f"  {key} = {value!r}")


# ---------------------------------------------------------------------------
# remote execution — Issue #110 Phase 6
# ---------------------------------------------------------------------------


@cli.group()
@click.option(
    "--host",
    default="bigblack",
    envvar="OPENDEVIATIONBAR_REMOTE_HOST",
    help="Remote host (default: bigblack)",
)
@click.pass_context
def remote(ctx: click.Context, host: str) -> None:
    """Execute commands on remote host via SSH."""
    ctx.ensure_object(dict)
    ctx.obj["host"] = host


@remote.command(name="populate")
@click.option("--no-pueue", is_flag=True, help="Run directly instead of via pueue")
@click.option("--group", "-g", type=str, default=None, help="Pueue group name")
@click.option("--label", "-l", type=str, default=None, help="Pueue job label")
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
@click.pass_context
def remote_populate(
    ctx: click.Context,
    no_pueue: bool,
    group: str | None,
    label: str | None,
    args: tuple[str, ...],
) -> None:
    """Queue populate on remote host via pueue (default).

    Uses pueue --working-directory instead of cd && to avoid shell
    quoting issues when SSH nests the command.  Issue #110 Phase 6.

    \b
    Examples:
        opendeviationbar remote populate -- range --symbol BTCUSDT \\
            --start 2024-01-01 --end 2024-12-31
        opendeviationbar remote populate --no-pueue -- year \\
            --symbol ETHUSDT --year 2024
    """
    import shlex
    import subprocess

    host = ctx.obj["host"]
    remote_dir = "/home/tca/opendeviationbar-py"
    populate_args = " ".join(shlex.quote(a) for a in args)
    populate_cmd = f"uv run --python 3.13 opendeviationbar populate {populate_args}"

    if no_pueue:
        ssh_cmd = f"cd {remote_dir} && {populate_cmd}"
    else:
        pueue_args = f" --working-directory {remote_dir}"
        if group:
            pueue_args += f" --group {group}"
        if label:
            pueue_args += f" --label {shlex.quote(label)}"
        ssh_cmd = f"pueue add{pueue_args} -- {populate_cmd}"

    click.echo(f"Remote ({host}): {ssh_cmd}")
    result = subprocess.run(
        ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", host, ssh_cmd],
        capture_output=True,
        text=True,
        check=False,
    )

    if result.stdout:
        click.echo(result.stdout.rstrip())
    if result.returncode != 0:
        if result.stderr:
            click.echo(result.stderr.rstrip(), err=True)
        sys.exit(result.returncode)


@remote.command(name="status")
@click.pass_context
def remote_status(ctx: click.Context) -> None:
    """Check pueue status on remote host."""
    import subprocess

    host = ctx.obj["host"]
    result = subprocess.run(
        ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", host, "pueue status"],
        capture_output=True,
        text=True,
        check=False,
    )

    if result.stdout:
        click.echo(result.stdout.rstrip())
    if result.returncode != 0:
        if result.stderr:
            click.echo(result.stderr.rstrip(), err=True)
        sys.exit(result.returncode)


def main() -> None:
    """Entry point for the CLI."""
    cli()


if __name__ == "__main__":
    main()
