#!/usr/bin/env python3
"""
Redis Queue Management CLI Tool for yt-ops-client.
"""

import argparse
import json
import logging
import os
import sys
from typing import Optional

import redis

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

try:
    from tabulate import tabulate
except ImportError:
    print("'tabulate' library not found. Please install it with: pip install tabulate", file=sys.stderr)
    tabulate = None

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


class QueueManager:
    """Manages Redis lists (queues)."""

    def __init__(self, redis_host='localhost', redis_port=6379, redis_password=None):
        """Initialize Redis connection."""
        logger.info(f"Attempting to connect to Redis at {redis_host}:{redis_port}...")
        try:
            self.redis = redis.Redis(
                host=redis_host,
                port=redis_port,
                password=redis_password,
                decode_responses=True,
                socket_connect_timeout=5,
                socket_timeout=5
            )
            self.redis.ping()
            logger.info(f"Successfully connected to Redis.")
        except redis.exceptions.ConnectionError as e:
            logger.error(f"Failed to connect to Redis at {redis_host}:{redis_port}: {e}")
            sys.exit(1)

    def list_queues(self, pattern: str):
        """Lists queues matching a pattern and their sizes."""
        queues = []
        for key in self.redis.scan_iter(match=pattern):
            key_type = self.redis.type(key)
            if key_type == 'list':
                size = self.redis.llen(key)
                queues.append({'name': key, 'size': size})
        return queues

    def peek(self, queue_name: str, count: int):
        """Returns the top `count` items from a queue without removing them."""
        return self.redis.lrange(queue_name, 0, count - 1)

    def count(self, queue_name: str) -> int:
        """Returns the number of items in a queue."""
        return self.redis.llen(queue_name)

    def populate(self, queue_name: str, file_path: str) -> int:
        """Populates a queue from a file (text with one item per line, or JSON with an array of strings)."""
        count = 0

        if file_path.lower().endswith('.json'):
            logger.info("Detected JSON file. Attempting to parse as an array of strings.")
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                if not isinstance(data, list):
                    logger.error("JSON file must contain a list/array.")
                    return 0
                
                items_to_add = [str(item).strip() for item in data if str(item).strip()]
                
                pipe = self.redis.pipeline()
                for item in items_to_add:
                    pipe.rpush(queue_name, item)
                    count += 1
                    if count % 1000 == 0:
                        pipe.execute()
                        logger.info(f"Pushed {count} items...")
                pipe.execute()
                
            except (IOError, json.JSONDecodeError) as e:
                logger.error(f"Failed to read or parse JSON file '{file_path}': {e}")
                return 0
        else:
            logger.info("Reading items from text file (one per line).")
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    pipe = self.redis.pipeline()
                    for line in f:
                        item = line.strip()
                        if item:
                            pipe.rpush(queue_name, item)
                            count += 1
                            if count % 1000 == 0:
                                pipe.execute()
                                logger.info(f"Pushed {count} items...")
                    pipe.execute()
            except IOError as e:
                logger.error(f"Failed to read file '{file_path}': {e}")
                return 0

        logger.info(f"Finished. Pushed a total of {count} items to '{queue_name}'.")
        return count

    def clear(self, queue_name: str, dump_path: Optional[str] = None) -> int:
        """Clears a queue, optionally dumping its contents to a file."""
        size = self.redis.llen(queue_name)
        if size == 0:
            logger.info(f"Queue '{queue_name}' is already empty.")
            return 0

        if dump_path:
            logger.info(f"Dumping {size} items from '{queue_name}' to '{dump_path}'...")
            with open(dump_path, 'w') as f:
                # Use lpop to be memory efficient for very large queues
                while True:
                    item = self.redis.lpop(queue_name)
                    if item is None:
                        break
                    f.write(item + '\n')
            logger.info("Dump complete.")
            # After lpop, the queue is already empty.
            return size

        deleted_count = self.redis.delete(queue_name)
        if deleted_count > 0:
            logger.info(f"Cleared queue '{queue_name}' ({size} items).")
        
        return size


def add_queue_manager_parser(subparsers):
    """Adds the parser for the 'queue' command."""
    parser = subparsers.add_parser(
        'queue',
        description='Manage Redis queues.',
        formatter_class=argparse.RawTextHelpFormatter,
        help='Manage Redis queues.'
    )

    # Common arguments for all queue manager subcommands
    common_parser = argparse.ArgumentParser(add_help=False)
    common_parser.add_argument('--env-file', help='Path to a .env file to load environment variables from.')
    common_parser.add_argument('--env', default='dev', help="Environment name for queue prefixes (e.g., 'stg', 'prod'). Defaults to 'dev'.")
    common_parser.add_argument('--redis-host', default=None, help='Redis host. Defaults to REDIS_HOST or MASTER_HOST_IP env var, or localhost.')
    common_parser.add_argument('--redis-port', type=int, default=None, help='Redis port. Defaults to REDIS_PORT env var, or 6379.')
    common_parser.add_argument('--redis-password', default=None, help='Redis password. Defaults to REDIS_PASSWORD env var.')
    common_parser.add_argument('--verbose', action='store_true', help='Enable verbose logging')

    subparsers = parser.add_subparsers(dest='queue_command', help='Command to execute', required=True)

    # List command
    list_parser = subparsers.add_parser('list', help='List queues and their sizes.', parents=[common_parser])
    list_parser.add_argument('--pattern', default='*queue*', help="Pattern to search for queue keys (default: '*queue*')")

    # Peek command
    peek_parser = subparsers.add_parser('peek', help='View items in a queue without removing them.', parents=[common_parser])
    peek_parser.add_argument('queue_name', nargs='?', help="Name of the queue. Defaults to '<env>_stress_inbox'.")
    peek_parser.add_argument('--count', type=int, default=10, help='Number of items to show (default: 10)')

    # Populate command
    populate_parser = subparsers.add_parser('populate', help='Populate a queue from a file (one item per line).', parents=[common_parser])
    populate_parser.add_argument('file_path', help='Path to the file containing items to add.')
    populate_parser.add_argument('--queue-name', help="Name of the queue to populate. Defaults to '<env>_stress_inbox'.")

    # Clear command
    clear_parser = subparsers.add_parser('clear', help='Clear a queue, optionally dumping its contents.', parents=[common_parser])
    clear_parser.add_argument('queue_name', nargs='?', help="Name of the queue to clear. Defaults to '<env>_stress_inbox'.")
    clear_parser.add_argument('--dump-to', help='File path to dump queue contents before clearing.')
    clear_parser.add_argument('--confirm', action='store_true', help='Confirm this destructive action (required).')

    return parser


def main_queue_manager(args):
    """Main dispatcher for 'queue' command."""
    if load_dotenv:
        was_loaded = load_dotenv(args.env_file)
        if was_loaded:
            print(f"Loaded environment variables from {args.env_file or '.env file'}", file=sys.stderr)
        elif args.env_file:
            print(f"ERROR: The specified --env-file was not found: {args.env_file}", file=sys.stderr)
            return 1

    if args.redis_host is None:
        args.redis_host = os.getenv('REDIS_HOST', os.getenv('MASTER_HOST_IP', 'localhost'))
    if args.redis_port is None:
        args.redis_port = int(os.getenv('REDIS_PORT', 6379))
    if args.redis_password is None:
        args.redis_password = os.getenv('REDIS_PASSWORD')

    if args.verbose:
        logging.getLogger().setLevel(logging.DEBUG)

    manager = QueueManager(
        redis_host=args.redis_host,
        redis_port=args.redis_port,
        redis_password=args.redis_password
    )

    # For commands that operate on a single queue, set a default name based on the environment if not provided.
    is_single_queue_command = args.queue_command in ['peek', 'populate', 'clear']
    if is_single_queue_command:
        # `populate` uses an option (--queue-name), while `peek` and `clear` use a positional argument.
        # We check for `queue_name` attribute and if it's falsy (None or empty string).
        if not getattr(args, 'queue_name', None):
            default_queue_name = f"{args.env}_stress_inbox"
            args.queue_name = default_queue_name
            print(f"INFO: No queue name specified, defaulting to '{default_queue_name}' based on --env='{args.env}'.", file=sys.stderr)

    if args.queue_command == 'list':
        queues = manager.list_queues(args.pattern)
        if not queues:
            print(f"No queues found matching pattern '{args.pattern}'.")
            return 0
        if tabulate:
            print(tabulate(queues, headers='keys', tablefmt='grid'))
        else:
            for q in queues:
                print(f"{q['name']}: {q['size']}")
        return 0

    elif args.queue_command == 'peek':
        size = manager.count(args.queue_name)
        items = manager.peek(args.queue_name, args.count)
        print(f"Queue '{args.queue_name}' has {size} items. Showing top {len(items)}:")
        for i, item in enumerate(items):
            print(f"{i+1: >3}: {item}")
        return 0

    elif args.queue_command == 'populate':
        if not os.path.exists(args.file_path):
            print(f"Error: File not found at '{args.file_path}'", file=sys.stderr)
            return 1
        manager.populate(args.queue_name, args.file_path)
        return 0

    elif args.queue_command == 'clear':
        if not args.confirm:
            print("Error: --confirm flag is required for this destructive action.", file=sys.stderr)
            return 1
        manager.clear(args.queue_name, args.dump_to)
        return 0

    return 1 # Should not be reached
