import os
import sys

import click

from . import (
    concurrency,
    db,
    error_handler,
    exceptions,
    executor,
    health,
    labels,
    logger,
    planner,
    repository,
    restore,
    schema,
)
from . import config as config_module


def _handle_snapshot_exists_error(
    error_details: dict,
    label: str,
    config: str,
    repository: str,
    backup_type: str,
    group: str,
    baseline_backup: str = None,
) -> None:
    """Handle snapshot_exists error by providing helpful guidance to the user.

    Args:
        error_details: Error details dict containing error_type and snapshot_name
        label: The backup label that was generated
        config: Path to config file
        repository: Repository name
        backup_type: Type of backup ('incremental' or 'full')
        group: Inventory group name
        baseline_backup: Optional baseline backup label (for incremental backups)
    """
    snapshot_name = error_details.get("snapshot_name", label)
    logger.error(f"Snapshot '{snapshot_name}' already exists in the repository.")
    logger.info("")
    logger.info("This typically happens when:")
    logger.info("  • The CLI lost connectivity during a previous backup operation")
    logger.info("  • The backup completed on the server, but backup_history wasn't updated")
    logger.info("")
    logger.info("To resolve this, retry the backup with a custom label using --name:")

    if backup_type == "incremental":
        retry_cmd = f"  starrocks-br backup incremental --config {config} --group {group} --name {snapshot_name}_retry"
        if baseline_backup:
            retry_cmd += f" --baseline-backup {baseline_backup}"
        logger.info(retry_cmd)
    else:
        logger.info(
            f"  starrocks-br backup full --config {config} --group {group} --name {snapshot_name}_retry"
        )

    logger.info("")
    logger.tip("You can verify the existing backup by checking the repository or running:")
    logger.tip(f"  SHOW SNAPSHOT ON {repository} WHERE Snapshot = '{snapshot_name}'")


@click.group()
@click.option("--verbose", is_flag=True, help="Enable verbose debug logging")
@click.pass_context
def cli(ctx, verbose):
    """StarRocks Backup & Restore automation tool."""
    ctx.ensure_object(dict)
    ctx.obj["verbose"] = verbose

    if verbose:
        import logging

        logger.setup_logging(level=logging.DEBUG)
        logger.debug("Verbose logging enabled")
    else:
        logger.setup_logging()


@cli.command("init")
@click.option("--config", required=True, help="Path to config YAML file")
def init(config):
    """Initialize ops database and control tables.

    Creates the ops database with required tables:
    - ops.table_inventory: Inventory groups mapping to databases/tables
    - ops.backup_history: Backup operation history
    - ops.restore_history: Restore operation history
    - ops.run_status: Job concurrency control

    Run this once before using backup/restore commands.
    """
    try:
        cfg = config_module.load_config(config)
        config_module.validate_config(cfg)

        database = db.StarRocksDB(
            host=cfg["host"],
            port=cfg["port"],
            user=cfg["user"],
            password=os.getenv("STARROCKS_PASSWORD"),
            database=cfg["database"],
            tls_config=cfg.get("tls"),
        )

        with database:
            logger.info("Initializing ops schema...")
            schema.initialize_ops_schema(database)
            logger.info("")
            logger.info("Next steps:")
            logger.info("1. Insert your table inventory records:")
            logger.info("   INSERT INTO ops.table_inventory")
            logger.info("   (inventory_group, database_name, table_name)")
            logger.info("   VALUES ('my_daily_incremental', 'your_db', 'your_fact_table');")
            logger.info("   VALUES ('my_full_database_backup', 'your_db', '*');")
            logger.info("   VALUES ('my_full_dimension_tables', 'your_db', 'dim_customers');")
            logger.info("   VALUES ('my_full_dimension_tables', 'your_db', 'dim_products');")
            logger.info("")
            logger.info("2. Run your first backup:")
            logger.info(
                "   starrocks-br backup incremental --group my_daily_incremental --config config.yaml"
            )

    except exceptions.ConfigFileNotFoundError as e:
        error_handler.handle_config_file_not_found_error(e)
        sys.exit(1)
    except exceptions.ConfigValidationError as e:
        error_handler.handle_config_validation_error(e, config)
        sys.exit(1)
    except FileNotFoundError as e:
        error_handler.handle_config_file_not_found_error(exceptions.ConfigFileNotFoundError(str(e)))
        sys.exit(1)
    except Exception as e:
        logger.error(f"Failed to initialize schema: {e}")
        sys.exit(1)


@cli.group()
def backup():
    """Backup commands."""
    pass


@backup.command("incremental")
@click.option("--config", required=True, help="Path to config YAML file")
@click.option(
    "--baseline-backup",
    help="Specific backup label to use as baseline (optional). If not provided, uses the latest successful full backup.",
)
@click.option(
    "--group",
    required=True,
    help="Inventory group to backup from table_inventory. Supports wildcard '*'.",
)
@click.option(
    "--name",
    help="Optional logical name (label) for the backup. Supports -v#r placeholder for auto-versioning.",
)
def backup_incremental(config, baseline_backup, group, name):
    """Run incremental backup of partitions changed since the latest full backup.

    By default, uses the latest successful full backup as baseline.
    Optionally specify a specific backup label to use as baseline.

    Flow: load config → check health → ensure repository → reserve job slot →
    find baseline backup → find recent partitions → generate label → build backup command → execute backup
    """
    try:
        cfg = config_module.load_config(config)
        config_module.validate_config(cfg)

        database = db.StarRocksDB(
            host=cfg["host"],
            port=cfg["port"],
            user=cfg["user"],
            password=os.getenv("STARROCKS_PASSWORD"),
            database=cfg["database"],
            tls_config=cfg.get("tls"),
        )

        with database:
            was_created = schema.ensure_ops_schema(database)
            if was_created:
                logger.warning(
                    "ops schema was auto-created. Please run 'starrocks-br init' after populating config."
                )
                logger.warning("Remember to populate ops.table_inventory with your backup groups!")
                sys.exit(1)  # Exit if schema was just created, requires user action

            healthy, message = health.check_cluster_health(database)
            if not healthy:
                logger.error(f"Cluster health check failed: {message}")
                sys.exit(1)

            logger.success(f"Cluster health: {message}")

            repository.ensure_repository(database, cfg["repository"])

            logger.success(f"Repository '{cfg['repository']}' verified")

            label = labels.determine_backup_label(
                db=database,
                backup_type="incremental",
                database_name=cfg["database"],
                custom_name=name,
            )

            logger.success(f"Generated label: {label}")

            if baseline_backup:
                logger.success(f"Using specified baseline backup: {baseline_backup}")
            else:
                latest_backup = planner.find_latest_full_backup(database, cfg["database"])
                if latest_backup:
                    logger.success(
                        f"Using latest full backup as baseline: {latest_backup['label']} ({latest_backup['backup_type']})"
                    )
                else:
                    logger.warning(
                        "No full backup found - this will be the first incremental backup"
                    )

            partitions = planner.find_recent_partitions(
                database, cfg["database"], baseline_backup_label=baseline_backup, group_name=group
            )

            if not partitions:
                logger.warning("No partitions found to backup")
                sys.exit(1)

            logger.success(f"Found {len(partitions)} partition(s) to backup")

            backup_command = planner.build_incremental_backup_command(
                partitions, cfg["repository"], label, cfg["database"]
            )

            concurrency.reserve_job_slot(database, scope="backup", label=label)

            planner.record_backup_partitions(database, label, partitions)

            logger.success("Job slot reserved")
            logger.info(f"Starting incremental backup for group '{group}'...")
            result = executor.execute_backup(
                database,
                backup_command,
                repository=cfg["repository"],
                backup_type="incremental",
                scope="backup",
                database=cfg["database"],
            )

            if result["success"]:
                logger.success(f"Backup completed successfully: {result['final_status']['state']}")
                sys.exit(0)
            else:
                error_details = result.get("error_details")
                if error_details and error_details.get("error_type") == "snapshot_exists":
                    _handle_snapshot_exists_error(
                        error_details,
                        label,
                        config,
                        cfg["repository"],
                        "incremental",
                        group,
                        baseline_backup,
                    )
                    sys.exit(1)

                state = result.get("final_status", {}).get("state", "UNKNOWN")
                if state == "LOST":
                    logger.critical("Backup tracking lost!")
                    logger.warning("Another backup operation started during ours.")
                    logger.tip("Enable ops.run_status concurrency checks to prevent this.")
                logger.error(f"{result['error_message']}")
                sys.exit(1)

    except exceptions.ConcurrencyConflictError as e:
        error_handler.handle_concurrency_conflict_error(e, config)
        sys.exit(1)
    except exceptions.BackupLabelNotFoundError as e:
        error_handler.handle_backup_label_not_found_error(e, config)
        sys.exit(1)
    except exceptions.NoFullBackupFoundError as e:
        error_handler.handle_no_full_backup_found_error(e, config, group)
        sys.exit(1)
    except exceptions.ConfigFileNotFoundError as e:
        error_handler.handle_config_file_not_found_error(e)
        sys.exit(1)
    except exceptions.ConfigValidationError as e:
        error_handler.handle_config_validation_error(e, config)
        sys.exit(1)
    except FileNotFoundError as e:
        error_handler.handle_config_file_not_found_error(exceptions.ConfigFileNotFoundError(str(e)))
        sys.exit(1)
    except ValueError as e:
        logger.error(f"Error: {e}")
        sys.exit(1)
    except Exception as e:
        logger.error(f"Unexpected error: {e}")
        sys.exit(1)


@backup.command("full")
@click.option("--config", required=True, help="Path to config YAML file")
@click.option(
    "--group",
    required=True,
    help="Inventory group to backup from table_inventory. Supports wildcard '*'.",
)
@click.option(
    "--name",
    help="Optional logical name (label) for the backup. Supports -v#r placeholder for auto-versioning.",
)
def backup_full(config, group, name):
    """Run a full backup for a specified inventory group.

    Flow: load config → check health → ensure repository → reserve job slot →
    find tables by group → generate label → build backup command → execute backup
    """
    try:
        cfg = config_module.load_config(config)
        config_module.validate_config(cfg)

        database = db.StarRocksDB(
            host=cfg["host"],
            port=cfg["port"],
            user=cfg["user"],
            password=os.getenv("STARROCKS_PASSWORD"),
            database=cfg["database"],
            tls_config=cfg.get("tls"),
        )

        with database:
            was_created = schema.ensure_ops_schema(database)
            if was_created:
                logger.warning(
                    "ops schema was auto-created. Please run 'starrocks-br init' after populating config."
                )
                logger.warning("Remember to populate ops.table_inventory with your backup groups!")
                sys.exit(1)  # Exit if schema was just created, requires user action

            healthy, message = health.check_cluster_health(database)
            if not healthy:
                logger.error(f"Cluster health check failed: {message}")
                sys.exit(1)

            logger.success(f"Cluster health: {message}")

            repository.ensure_repository(database, cfg["repository"])

            logger.success(f"Repository '{cfg['repository']}' verified")

            label = labels.determine_backup_label(
                db=database, backup_type="full", database_name=cfg["database"], custom_name=name
            )

            logger.success(f"Generated label: {label}")

            backup_command = planner.build_full_backup_command(
                database, group, cfg["repository"], label, cfg["database"]
            )

            if not backup_command:
                logger.warning(
                    f"No tables found in group '{group}' for database '{cfg['database']}' to backup"
                )
                sys.exit(1)

            tables = planner.find_tables_by_group(database, group)
            all_partitions = planner.get_all_partitions_for_tables(
                database, cfg["database"], tables
            )

            concurrency.reserve_job_slot(database, scope="backup", label=label)

            planner.record_backup_partitions(database, label, all_partitions)

            logger.success("Job slot reserved")
            logger.info(f"Starting full backup for group '{group}'...")
            result = executor.execute_backup(
                database,
                backup_command,
                repository=cfg["repository"],
                backup_type="full",
                scope="backup",
                database=cfg["database"],
            )

            if result["success"]:
                logger.success(f"Backup completed successfully: {result['final_status']['state']}")
                sys.exit(0)
            else:
                error_details = result.get("error_details")
                if error_details and error_details.get("error_type") == "snapshot_exists":
                    _handle_snapshot_exists_error(
                        error_details, label, config, cfg["repository"], "full", group
                    )
                    sys.exit(1)

                state = result.get("final_status", {}).get("state", "UNKNOWN")
                if state == "LOST":
                    logger.critical("Backup tracking lost!")
                    logger.warning("Another backup operation started during ours.")
                    logger.tip("Enable ops.run_status concurrency checks to prevent this.")
                logger.error(f"{result['error_message']}")
                sys.exit(1)

    except exceptions.ConcurrencyConflictError as e:
        error_handler.handle_concurrency_conflict_error(e, config)
        sys.exit(1)
    except exceptions.ConfigFileNotFoundError as e:
        error_handler.handle_config_file_not_found_error(e)
        sys.exit(1)
    except exceptions.ConfigValidationError as e:
        error_handler.handle_config_validation_error(e, config)
        sys.exit(1)
    except FileNotFoundError as e:
        error_handler.handle_config_file_not_found_error(exceptions.ConfigFileNotFoundError(str(e)))
        sys.exit(1)
    except ValueError as e:
        logger.error(f"Error: {e}")
        sys.exit(1)
    except Exception as e:
        logger.error(f"Unexpected error: {e}")
        sys.exit(1)


@cli.command("restore")
@click.option("--config", required=True, help="Path to config YAML file")
@click.option("--target-label", required=True, help="Backup label to restore to")
@click.option("--group", help="Optional inventory group to filter tables to restore")
@click.option(
    "--table",
    help="Optional table name to restore (table name only, database comes from config). Cannot be used with --group.",
)
@click.option(
    "--rename-suffix",
    default="_restored",
    help="Suffix for temporary tables during restore (default: _restored)",
)
@click.option("--yes", is_flag=True, help="Skip confirmation prompt and proceed automatically")
def restore_command(config, target_label, group, table, rename_suffix, yes):
    """Restore data to a specific point in time using intelligent backup chain resolution.

    This command automatically determines the correct sequence of backups needed for restore:
    - For full backups: restores directly from the target backup
    - For incremental backups: restores the base full backup first, then applies the incremental

    The restore process uses temporary tables with the specified suffix for safety, then performs
    an atomic rename to make the restored data live.

    Flow: load config → check health → ensure repository → find restore pair → get tables from backup → execute restore flow
    """
    try:
        if group and table:
            logger.error(
                "Cannot specify both --group and --table. Use --table for single table restore or --group for inventory group restore."
            )
            sys.exit(1)

        if table:
            table = table.strip()
            if not table:
                raise exceptions.InvalidTableNameError("", "Table name cannot be empty")

            if "." in table:
                raise exceptions.InvalidTableNameError(
                    table,
                    "Table name must not include database prefix. Use 'table_name' not 'database.table_name'",
                )

        cfg = config_module.load_config(config)
        config_module.validate_config(cfg)

        database = db.StarRocksDB(
            host=cfg["host"],
            port=cfg["port"],
            user=cfg["user"],
            password=os.getenv("STARROCKS_PASSWORD"),
            database=cfg["database"],
            tls_config=cfg.get("tls"),
        )

        with database:
            was_created = schema.ensure_ops_schema(database)
            if was_created:
                logger.warning(
                    "ops schema was auto-created. Please run 'starrocks-br init' after populating config."
                )
                logger.warning("Remember to populate ops.table_inventory with your backup groups!")
                sys.exit(1)  # Exit if schema was just created, requires user action

            healthy, message = health.check_cluster_health(database)
            if not healthy:
                logger.error(f"Cluster health check failed: {message}")
                sys.exit(1)

            logger.success(f"Cluster health: {message}")

            repository.ensure_repository(database, cfg["repository"])

            logger.success(f"Repository '{cfg['repository']}' verified")

            logger.info(f"Finding restore sequence for target backup: {target_label}")

            restore_pair = restore.find_restore_pair(database, target_label)
            logger.success(f"Found restore sequence: {' -> '.join(restore_pair)}")

            logger.info("Determining tables to restore from backup manifest...")

            tables_to_restore = restore.get_tables_from_backup(
                database,
                target_label,
                group=group,
                table=table,
                database=cfg["database"] if table else None,
            )

            if not tables_to_restore:
                raise exceptions.NoTablesFoundError(group=group, label=target_label)

            logger.success(
                f"Found {len(tables_to_restore)} table(s) to restore: {', '.join(tables_to_restore)}"
            )

            logger.info("Starting restore flow...")
            result = restore.execute_restore_flow(
                database,
                cfg["repository"],
                restore_pair,
                tables_to_restore,
                rename_suffix,
                skip_confirmation=yes,
            )

            if result["success"]:
                logger.success(result["message"])
                sys.exit(0)
            else:
                logger.error(f"Restore failed: {result['error_message']}")
                sys.exit(1)

    except exceptions.InvalidTableNameError as e:
        error_handler.handle_invalid_table_name_error(e)
        sys.exit(1)
    except exceptions.BackupLabelNotFoundError as e:
        error_handler.handle_backup_label_not_found_error(e, config)
        sys.exit(1)
    except exceptions.NoSuccessfulFullBackupFoundError as e:
        error_handler.handle_no_successful_full_backup_found_error(e, config)
        sys.exit(1)
    except exceptions.TableNotFoundInBackupError as e:
        error_handler.handle_table_not_found_in_backup_error(e, config)
        sys.exit(1)
    except exceptions.NoTablesFoundError as e:
        error_handler.handle_no_tables_found_error(e, config, target_label)
        sys.exit(1)
    except exceptions.SnapshotNotFoundError as e:
        error_handler.handle_snapshot_not_found_error(e, config)
        sys.exit(1)
    except exceptions.RestoreOperationCancelledError:
        error_handler.handle_restore_operation_cancelled_error()
        sys.exit(1)
    except exceptions.ConfigFileNotFoundError as e:
        error_handler.handle_config_file_not_found_error(e)
        sys.exit(1)
    except exceptions.ConfigValidationError as e:
        error_handler.handle_config_validation_error(e, config)
        sys.exit(1)
    except exceptions.ClusterHealthCheckFailedError as e:
        error_handler.handle_cluster_health_check_failed_error(e, config)
        sys.exit(1)
    except FileNotFoundError as e:
        error_handler.handle_config_file_not_found_error(exceptions.ConfigFileNotFoundError(str(e)))
        sys.exit(1)
    except ValueError as e:
        error_handler.handle_config_validation_error(
            exceptions.ConfigValidationError(str(e)), config
        )
        sys.exit(1)
    except exceptions.ConcurrencyConflictError as e:
        error_handler.handle_concurrency_conflict_error(e, config)
        sys.exit(1)
    except Exception as e:
        logger.error(f"Unexpected error: {e}")
        sys.exit(1)


if __name__ == "__main__":
    cli()
