"""Database Commands - Execute SQL through encrypted tunnels"""
import json
import os
import subprocess
import tempfile
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Optional

from navig import console_helper as ch


def _create_mysql_config_file(user: str, password: str) -> str:
    """
    Create temporary MySQL config file with credentials.
    Returns path to config file.
    SECURITY: Prevents password from appearing in process listings.
    """
    fd, config_path = tempfile.mkstemp(suffix='.cnf', text=True)
    try:
        with os.fdopen(fd, 'w') as f:
            f.write("[client]\n")
            f.write(f"user={user}\n")
            f.write(f"password={password}\n")
        # Set restrictive permissions (owner read/write only)
        os.chmod(config_path, 0o600)
        return config_path
    except Exception:
        try:
            os.unlink(config_path)
        except OSError:
            pass  # best-effort cleanup
        raise


def execute_sql(query: str, options: Dict[str, Any]):
    """Execute SQL query through tunnel."""
    from navig.config import get_config_manager
    from navig.tunnel import TunnelManager

    config_manager = get_config_manager()
    tunnel_manager = TunnelManager(config_manager)

    server_name = options.get('app') or config_manager.get_active_server()
    if not server_name:
        ch.error("No active server.")
        return

    dry_run = options.get('dry_run', False)
    json_enabled = options.get('json', False)

    if dry_run:
        if json_enabled:
            ch.raw_print(json.dumps({"dry_run": True, "action": "execute_sql", "query": query}))
        else:
            ch.info(f"[DRY RUN] Would execute SQL: {query}")
        return

    # Check if SQL query requires confirmation based on configured level
    query_type = ch.classify_sql(query)
    query_preview = query if len(query) < 80 else query[:80] + "..."

    if not ch.confirm_operation(
        operation_name=f"SQL: {query_preview}",
        operation_type=query_type,
        host=server_name,
        auto_confirm=options.get('yes', False),
        force_confirm=options.get('confirm', False),
    ):
        ch.warning("Cancelled.")
        return

    # Ensure tunnel is running
    tunnel_info = tunnel_manager.get_tunnel_status(server_name)
    if not tunnel_info:
        ch.warning("Starting tunnel...")
        tunnel_info = tunnel_manager.start_tunnel(server_name)

    server_config = config_manager.load_server_config(server_name)
    db = server_config['database']

    # Create secure config file (prevents password in process listings)
    config_file = _create_mysql_config_file(db['user'], db['password'])

    try:
        # Execute via mysql client
        mysql_cmd = [
            'mysql',
            f'--defaults-file={config_file}',
            '-h', '127.0.0.1',
            '-P', str(tunnel_info['local_port']),
            db['name'],
            '-e', query
        ]

        try:
            result = subprocess.run(mysql_cmd, capture_output=True, text=True)
            if json_enabled:
                ch.raw_print(json.dumps({
                    "query": query,
                    "success": result.returncode == 0,
                    "output": result.stdout if result.returncode == 0 else None,
                    "error": result.stderr if result.returncode != 0 else None
                }))
            else:
                if result.returncode == 0:
                    ch.raw_print(result.stdout)
                else:
                    ch.error(f"SQL Error: {result.stderr}")
        except FileNotFoundError:
            if json_enabled:
                ch.raw_print(json.dumps({"success": False, "error": "mysql client not found"}))
            else:
                ch.error("mysql client not found. Please install MySQL client tools.")
    finally:
        # Always cleanup temp config file
        try:
            os.unlink(config_file)
        except OSError:
            pass  # best-effort cleanup


def execute_sql_file(file: Path, options: Dict[str, Any]):
    """Execute SQL file through tunnel."""
    if not file.exists():
        ch.error(f"File not found: {file}")
        return

    query = file.read_text()
    execute_sql(query, options)


def backup_database(path: Optional[Path], options: Dict[str, Any]):
    """Backup database."""
    from navig.config import get_config_manager
    from navig.tunnel import TunnelManager

    config_manager = get_config_manager()
    tunnel_manager = TunnelManager(config_manager)

    server_name = options.get('app') or config_manager.get_active_server()
    if not server_name:
        ch.error("No active server.")
        return

    dry_run = options.get('dry_run', False)
    json_enabled = options.get('json', False)

    # Default path
    if path is None:
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        path = config_manager.backups_dir / f"{server_name}_{timestamp}.sql"

    # Ensure tunnel
    tunnel_info = tunnel_manager.get_tunnel_status(server_name)
    if not tunnel_info:
        tunnel_info = tunnel_manager.start_tunnel(server_name)

    server_config = config_manager.load_server_config(server_name)
    db = server_config['database']

    if dry_run:
        if json_enabled:
            ch.raw_print(json.dumps({"dry_run": True, "action": "backup", "database": db['name'], "path": str(path)}))
        else:
            ch.info(f"[DRY RUN] Would backup database: {db['name']}")
            ch.dim(f"[DRY RUN] Destination: {path}")
        return

    if not json_enabled:
        ch.info(f"Creating backup: {path}")

    # Create secure config file (prevents password in process listings)
    config_file = _create_mysql_config_file(db['user'], db['password'])

    try:
        mysqldump_cmd = [
            'mysqldump',
            f'--defaults-file={config_file}',
            '-h', '127.0.0.1',
            '-P', str(tunnel_info['local_port']),
            db['name']
        ]

        try:
            with open(path, 'w') as f:
                result = subprocess.run(mysqldump_cmd, stdout=f, stderr=subprocess.PIPE, text=True)

            if result.returncode == 0:
                size = path.stat().st_size
                if json_enabled:
                    ch.raw_print(json.dumps({"success": True, "database": db['name'], "path": str(path), "size_bytes": size}))
                else:
                    ch.success(f"✓ Backup complete: {size:,} bytes")
            else:
                if json_enabled:
                    ch.raw_print(json.dumps({"success": False, "error": result.stderr}))
                else:
                    ch.error(f"Backup failed: {result.stderr}")
        except FileNotFoundError:
            if json_enabled:
                ch.raw_print(json.dumps({"success": False, "error": "mysqldump not found"}))
            else:
                ch.error("mysqldump not found. Please install MySQL client tools.")
            ch.info("")
            ch.info("Installation instructions:")
            ch.info("  Windows: choco install mysql")
            ch.info("  macOS:   brew install mysql-client")
            ch.info("  Ubuntu:  sudo apt-get install mysql-client")
            ch.info("  CentOS:  sudo yum install mysql")
            ch.info("")
            ch.info("After installation, restart your terminal.")
    finally:
        # Always cleanup temp config file
        try:
            os.unlink(config_file)
        except OSError:
            pass  # best-effort cleanup


def restore_database(file: Path, options: Dict[str, Any]):
    """Restore database from backup file with transaction support."""
    from navig.config import get_config_manager
    from navig.tunnel import TunnelManager

    if not file.exists():
        ch.error(f"File not found: {file}")
        return

    config_manager = get_config_manager()
    tunnel_manager = TunnelManager(config_manager)

    server_name = options.get('app') or config_manager.get_active_server()
    if not server_name:
        ch.error("No active server.")
        return

    dry_run = options.get('dry_run', False)
    json_enabled = options.get('json', False)

    # Safety check - verify backup file integrity if checksum exists
    backup_dir = file.parent
    metadata_file = backup_dir / "metadata.json"

    if dry_run:
        if json_enabled:
            ch.raw_print(json.dumps({"dry_run": True, "action": "restore", "file": str(file), "warning": "This will OVERWRITE the database"}))
        else:
            ch.warning(f"[DRY RUN] Would restore database from: {file}")
            ch.warning("[DRY RUN] This will REPLACE all data in the database")
        return

    if metadata_file.exists():
        try:
            with open(metadata_file) as f:
                metadata = json.load(f)
                # Find checksum for this file
                for db_info in metadata.get('databases', []):
                    if db_info.get('file') == file.name and 'checksum' in db_info:
                        ch.info("Verifying backup integrity...")
                        actual_checksum = _calculate_file_checksum(file)
                        if actual_checksum != db_info['checksum']:
                            ch.error("Backup file corrupted! Checksum mismatch.")
                            ch.error(f"Expected: {db_info['checksum'][:16]}...")
                            ch.error(f"Actual: {actual_checksum[:16]}...")
                            if not options.get('force'):
                                ch.error("Restore cancelled. Use --force to override.")
                                return
                        else:
                            ch.success("✓ Backup integrity verified")
                        break
        except (json.JSONDecodeError, OSError) as e:
            ch.warning(f"Could not verify backup integrity: {e}")

    ch.warning("⚠️  DESTRUCTIVE OPERATION")
    ch.warning("   This will REPLACE all data in the database")
    ch.warning(f"   Backup file: {file.name}")
    ch.warning(f"   Size: {file.stat().st_size / (1024 * 1024):.2f} MB")

    if not options.get('yes'):
        confirm = input("\nType 'RESTORE' to confirm: ")
        if confirm != 'RESTORE':
            if json_enabled:
                ch.raw_print(json.dumps({"success": False, "cancelled": True}))
            else:
                ch.warning("Restore cancelled.")
            return

    # Ensure tunnel
    tunnel_info = tunnel_manager.get_tunnel_status(server_name)
    if not tunnel_info:
        ch.info("Starting tunnel...")
        tunnel_info = tunnel_manager.start_tunnel(server_name)

    server_config = config_manager.load_server_config(server_name)
    db = server_config['database']

    # Create backup of current state before restore
    if not options.get('no_backup'):
        ch.info("Creating safety backup of current database...")
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        safety_backup = config_manager.backups_dir / f"{server_name}_pre_restore_{timestamp}.sql"
        backup_database(safety_backup, options)
        ch.success(f"✓ Safety backup created: {safety_backup.name}")

    ch.info("Restoring database...")

    # Create secure config file
    config_file = _create_mysql_config_file(db['user'], db['password'])

    try:
        # Restore with error handling
        mysql_cmd = [
            'mysql',
            f'--defaults-file={config_file}',
            '-h', '127.0.0.1',
            '-P', str(tunnel_info['local_port']),
            db['name']
        ]

        try:
            with open(file, 'r') as f:
                result = subprocess.run(
                    mysql_cmd,
                    stdin=f,
                    capture_output=True,
                    text=True
                )

            if result.returncode == 0:
                if json_enabled:
                    output = {"success": True, "database": db['name'], "source": str(file)}
                    if not options.get('no_backup'):
                        output["safety_backup"] = str(safety_backup.name)
                    ch.raw_print(json.dumps(output))
                else:
                    ch.success("✅ Database restored successfully")
                    ch.info(f"   Restored from: {file.name}")
                    if not options.get('no_backup'):
                        ch.info(f"   Safety backup: {safety_backup.name}")
            else:
                if json_enabled:
                    ch.raw_print(json.dumps({"success": False, "error": result.stderr}))
                else:
                    ch.error("❌ Restore failed")
                    ch.error(f"Error: {result.stderr}")
                    if not options.get('no_backup'):
                        ch.warning(f"You can rollback using: navig restore {safety_backup}")

        except FileNotFoundError:
            if json_enabled:
                ch.raw_print(json.dumps({"success": False, "error": "mysql client not found"}))
            else:
                ch.error("mysql client not found. Please install MySQL client tools.")
    finally:
        # Always cleanup temp config file
        try:
            os.unlink(config_file)
        except OSError:
            pass  # best-effort cleanup


def _calculate_file_checksum(file_path: Path, algorithm: str = 'sha256') -> str:
    """Calculate checksum for file integrity verification."""
    import hashlib

    hash_obj = hashlib.new(algorithm)
    with open(file_path, 'rb') as f:
        for chunk in iter(lambda: f.read(8192), b''):
            hash_obj.update(chunk)
    return hash_obj.hexdigest()


