#!/usr/bin/env python3
"""
CLI tool to set up profiles from a YAML policy file.
"""

import argparse
import json
import logging
import os
import subprocess
import sys
from typing import List

try:
    import yaml
except ImportError:
    print("PyYAML is not installed. Please install it with: pip install PyYAML", file=sys.stderr)
    yaml = None

try:
    from dotenv import load_dotenv
except ImportError:
    load_dotenv = None

logger = logging.getLogger(__name__)

def run_command(cmd: List[str], capture: bool = False):
    """Runs a command and raises an exception on failure."""
    logger.debug(f"Running command: {' '.join(cmd)}")
    # check=True will raise CalledProcessError on non-zero exit codes
    result = subprocess.run(
        cmd, 
        capture_output=capture, 
        text=True,
        check=True
    )
    return result

def add_setup_profiles_parser(subparsers):
    """Adds the parser for the 'setup-profiles' command."""
    parser = subparsers.add_parser(
        'setup-profiles',
        description="Set up profiles for a simulation or test run based on a policy file.",
        formatter_class=argparse.RawTextHelpFormatter,
        help="Set up profiles from a policy file."
    )
    parser.add_argument('--policy', '--policy-file', dest='policy_file', required=True, help="Path to the YAML profile setup policy file.")
    parser.add_argument('--env', help="Override the environment name from the policy file. For multi-setup files, this will override the 'env' for ALL setups being run.")
    parser.add_argument('--env-file', help="Override the env_file setting in the policy.")
    parser.add_argument('--auth-only', action='store_true', help='In a multi-setup policy file, run only the auth_profile_setup.')
    parser.add_argument('--download-only', action='store_true', help='In a multi-setup policy file, run only the download_profile_setup.')
    parser.add_argument('--redis-host', default=None, help='Redis host. Overrides policy and .env file.')
    parser.add_argument('--redis-port', type=int, default=None, help='Redis port. Overrides policy and .env file.')
    parser.add_argument('--redis-password', default=None, help='Redis password. Overrides policy and .env file.')
    parser.add_argument('--preserve-profiles', action='store_true', help="Do not clean up existing profiles; create only what is missing.")
    parser.add_argument('--cleanup-prefix', action='append', help="Prefix of profiles to delete before setup. Can be specified multiple times. Overrides policy-based cleanup.")
    parser.add_argument('--cleanup-all', action='store_true', help="(Destructive) Delete ALL data for the environment (profiles, proxies, counters) before setup. Overrides all other cleanup options.")
    parser.add_argument('--reset-global-counters', action='store_true', help="Reset global counters like 'failed_lock_attempts'.")
    parser.add_argument('--verbose', action='store_true', help="Enable verbose logging.")
    return parser

def _run_setup_for_env(profile_setup: dict, common_args: list, args: argparse.Namespace) -> int:
    """Runs the profile setup logic for a given configuration block."""
    if args.cleanup_all:
        logger.info("--- (DESTRUCTIVE) Cleaning up all data for the environment via --cleanup-all ---")
        try:
            cleanup_cmd = ['bin/ytops-client', 'profile', 'delete-all', '--confirm'] + common_args
            run_command(cleanup_cmd)
        except subprocess.CalledProcessError as e:
            logger.error(f"Failed to clean up all data for the environment: {e}")
            return 1 # Stop if cleanup fails
        
        # Disable other cleanup logic
        profile_setup['cleanup_before_run'] = False
        args.cleanup_prefix = None
    
    # If --cleanup-prefix is provided, it takes precedence over policy settings
    if args.cleanup_prefix:
        logger.info("--- Cleaning up profiles based on --cleanup-prefix ---")
        for prefix in args.cleanup_prefix:
            try:
                list_cmd = ['bin/ytops-client', 'profile', 'list', '--format', 'json'] + common_args
                result = run_command(list_cmd, capture=True)
                profiles_to_delete = [p for p in json.loads(result.stdout) if p['name'].startswith(prefix)]
                
                if not profiles_to_delete:
                    logger.info(f"No profiles with prefix '{prefix}' found to delete.")
                    continue

                logger.info(f"Found {len(profiles_to_delete)} profiles with prefix '{prefix}' to delete.")
                for p in profiles_to_delete:
                    delete_cmd = ['bin/ytops-client', 'profile', 'delete', p['name'], '--confirm'] + common_args
                    run_command(delete_cmd)
            except (subprocess.CalledProcessError, json.JSONDecodeError) as e:
                logger.warning(f"Could not list or parse existing profiles with prefix '{prefix}' for cleanup. Error: {e}")
        
        # Disable policy-based cleanup as we've handled it via CLI
        profile_setup['cleanup_before_run'] = False

    if args.preserve_profiles:
        if profile_setup.get('cleanup_before_run'):
            logger.info("--preserve-profiles is set, overriding 'cleanup_before_run: true' from policy.")
            profile_setup['cleanup_before_run'] = False

    if args.reset_global_counters:
        logger.info("--- Resetting global counters ---")
        try:
            reset_cmd = ['bin/ytops-client', 'profile', 'reset-global-counters'] + common_args
            run_command(reset_cmd)
        except subprocess.CalledProcessError as e:
            logger.error(f"Failed to reset global counters: {e}")

    if profile_setup.get('cleanup_before_run'):
        logger.info("--- Cleaning up old profiles ---")
        for pool in profile_setup.get('pools', []):
            prefix = pool.get('prefix')
            if prefix:
                try:
                    list_cmd = ['bin/ytops-client', 'profile', 'list', '--format', 'json'] + common_args
                    result = run_command(list_cmd, capture=True)
                    profiles = [p for p in json.loads(result.stdout) if p['name'].startswith(prefix)]
                    
                    if not profiles:
                        logger.info(f"No profiles with prefix '{prefix}' found to delete.")
                        continue

                    logger.info(f"Found {len(profiles)} profiles with prefix '{prefix}' to delete.")
                    for p in profiles:
                        delete_cmd = ['bin/ytops-client', 'profile', 'delete', p['name'], '--confirm'] + common_args
                        run_command(delete_cmd)
                except (subprocess.CalledProcessError, json.JSONDecodeError) as e:
                    logger.warning(f"Could not list existing profiles with prefix '{prefix}' for cleanup. Assuming none exist. Error: {e}")

    existing_profiles = set()
    if not profile_setup.get('cleanup_before_run'):
        logger.info("--- Checking for existing profiles ---")
        try:
            list_cmd = ['bin/ytops-client', 'profile', 'list', '--format', 'json'] + common_args
            result = run_command(list_cmd, capture=True)
            profiles_data = json.loads(result.stdout)
            existing_profiles = {p['name'] for p in profiles_data}
            logger.info(f"Found {len(existing_profiles)} existing profiles.")
        except (subprocess.CalledProcessError, json.JSONDecodeError) as e:
            logger.error(f"Failed to list existing profiles. Will attempt to create all profiles. Error: {e}")

    logger.info("--- Creating new profiles (if needed) ---")
    for pool in profile_setup.get('pools', []):
        prefix = pool.get('prefix')
        proxy = pool.get('proxy')
        count = pool.get('count', 0)
        start_in_rest_minutes = pool.get('start_in_rest_for_minutes')

        profiles_in_pool_created = 0
        # Get a list of all profiles that should exist for this pool
        profile_names_in_pool = [f"{prefix}_{i}" for i in range(count)]

        for profile_name in profile_names_in_pool:
            if profile_name in existing_profiles:
                logger.debug(f"Profile '{profile_name}' already exists, preserving.")
                continue

            try:
                create_cmd = ['bin/ytops-client', 'profile', 'create', profile_name, proxy] + common_args
                run_command(create_cmd)
                profiles_in_pool_created += 1
            except subprocess.CalledProcessError as e:
                logger.error(f"Failed to create profile '{profile_name}': {e}")
        
        if profiles_in_pool_created > 0:
            logger.info(f"Created {profiles_in_pool_created} new profile(s) for pool '{prefix}'.")
        elif count > 0:
            logger.info(f"No new profiles needed for pool '{prefix}'. All {count} profile(s) already exist.")

        # If requested, put the proxy for this pool into a RESTING state.
        # This is done even for existing profiles when --preserve-profiles is used.
        if start_in_rest_minutes and proxy:
            logger.info(f"Setting proxy '{proxy}' for pool '{prefix}' to start in RESTING state for {start_in_rest_minutes} minutes.")
            try:
                # The 'set-proxy-state' command takes '--duration-minutes'
                set_state_cmd = ['bin/ytops-client', 'profile', 'set-proxy-state', proxy, 'RESTING',
                                 '--duration-minutes', str(start_in_rest_minutes)] + common_args
                run_command(set_state_cmd)
            except subprocess.CalledProcessError as e:
                logger.error(f"Failed to set initial REST state for proxy '{proxy}': {e}")
    
    return 0


def main_setup_profiles(args):
    """Main logic for the 'setup-profiles' command."""
    if not yaml: return 1

    if args.verbose:
        logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        logger.setLevel(logging.DEBUG)
    else:
        if not logging.getLogger().handlers:
            logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        logger.setLevel(logging.INFO)

    try:
        with open(args.policy_file, 'r', encoding='utf-8') as f:
            policy = yaml.safe_load(f) or {}
    except (IOError, yaml.YAMLError) as e:
        logger.error(f"Failed to load or parse policy file {args.policy_file}: {e}")
        return 1

    sim_params = policy.get('simulation_parameters', {})
    setups_to_run = []

    if not args.download_only and 'auth_profile_setup' in policy:
        setups_to_run.append(('Auth', policy['auth_profile_setup']))
    
    if not args.auth_only and 'download_profile_setup' in policy:
        setups_to_run.append(('Download', policy['download_profile_setup']))

    # Backward compatibility for old single-block format
    if not setups_to_run and 'profile_setup' in policy:
        legacy_config = policy['profile_setup']
        # Synthesize the env from the global section for the legacy block
        legacy_config['env'] = args.env or sim_params.get('env')
        setups_to_run.append(('Legacy', legacy_config))

    if not setups_to_run:
        logger.error("No 'auth_profile_setup', 'download_profile_setup', or legacy 'profile_setup' block found in policy file.")
        return 1

    env_file = args.env_file or sim_params.get('env_file')
    if load_dotenv:
        if load_dotenv(env_file):
            print(f"Loaded environment variables from {env_file or '.env file'}", file=sys.stderr)
        elif args.env_file and not os.path.exists(args.env_file):
            print(f"Error: The specified env_file was not found: {args.env_file}", file=sys.stderr)
            return 1

    base_common_args = []
    if env_file: base_common_args.extend(['--env-file', env_file])
    
    redis_host = args.redis_host or os.getenv('REDIS_HOST') or os.getenv('MASTER_HOST_IP') or sim_params.get('redis_host')
    if redis_host: base_common_args.extend(['--redis-host', redis_host])
    
    redis_port = args.redis_port
    if redis_port is None:
        redis_port_env = os.getenv('REDIS_PORT')
        redis_port = int(redis_port_env) if redis_port_env and redis_port_env.isdigit() else sim_params.get('redis_port')
    if redis_port: base_common_args.extend(['--redis-port', str(redis_port)])
    
    redis_password = args.redis_password or os.getenv('REDIS_PASSWORD') or sim_params.get('redis_password')
    if redis_password: base_common_args.extend(['--redis-password', redis_password])

    if args.verbose: base_common_args.append('--verbose')

    for setup_name, setup_config in setups_to_run:
        logger.info(f"--- Running setup for {setup_name} simulation ---")
        
        effective_env = args.env or setup_config.get('env')
        if not effective_env:
            logger.error(f"Could not determine environment for '{setup_name}' setup. Please specify 'env' in the policy block or via --env.")
            return 1
        
        env_common_args = base_common_args + ['--env', effective_env]
        
        if _run_setup_for_env(setup_config, env_common_args, args) != 0:
            return 1

    logger.info("\n--- All profile setups complete. ---")
    logger.info("You can now run the policy enforcer to manage the profiles:")
    logger.info("e.g., bin/ytops-client policy-enforcer --policy-file policies/8_unified_simulation_enforcer.yaml --live")
    return 0
