#!/usr/bin/env python3
"""
Interactive service management tool for CDC pipeline.

Usage:
    cdc manage-service                    # Interactive mode
    cdc manage-service --service adopus --inspect-mssql --all     # List all MSSQL tables (all schemas)
    cdc manage-service --service adopus --inspect-mssql --schema dbo  # List tables in specific schema
    cdc manage-service --service adopus --add-table Actor         # Add table to service
    cdc manage-service --service adopus --remove-table Test       # Remove table from service
    cdc manage-service --service adopus --generate-validation --all  # Generate JSON schema
"""

import argparse
import sys
from pathlib import Path
from typing import Any, Dict, List, Tuple, cast

try:
    import yaml  # type: ignore
except ImportError:
    yaml = None  # type: ignore[assignment]

from cdc_generator.helpers.helpers_logging import (
    Colors,
    print_header,
    print_info,
    print_success,
    print_warning,
    print_error,
)
from cdc_generator.validators.manage_service.mssql_inspector import inspect_mssql_schema  
from cdc_generator.validators.manage_service.postgres_inspector import inspect_postgres_schema  
from cdc_generator.validators.manage_service.table_operations import (
    add_table_to_service,
    remove_table_from_service,
)
from cdc_generator.validators.manage_service.validation import (
    validate_service_config,
    validate_hierarchy_no_duplicates,
)
from cdc_generator.validators.manage_service.service_creator import create_service
from cdc_generator.validators.manage_service.schema_generator import generate_service_validation_schema
from cdc_generator.validators.manage_service.interactive_mode import run_interactive_mode  
from cdc_generator.validators.manage_service.schema_saver import save_detailed_schema  

PROJECT_ROOT = Path(__file__).parent.parent
SERVICE_SCHEMAS_DIR = PROJECT_ROOT / "service-schemas"


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Interactive CDC service management tool",
        epilog="""
Examples:
  # Create a new service
  cdc manage-service --service myservice --create-service
  
  # Inspect database tables
  cdc manage-service --service adopus --inspect --schema dbo
  cdc manage-service --service adopus --inspect --all
  
  # Add tables to service
  cdc manage-service --service adopus --add-source-table dbo.Actor
  cdc manage-service --service adopus --add-source-tables dbo.Actor dbo.Fraver
  
  # Remove table from service
  cdc manage-service --service adopus --remove-table dbo.Actor
  
  # Validate service configuration
  cdc manage-service --service adopus --validate-config
        """,
        formatter_class=argparse.RawDescriptionHelpFormatter
    )
    parser.add_argument("--service", required=False, help="Service name from services/*.yaml (required for most operations)")
    parser.add_argument("--create-service", action="store_true", help="Create a new service configuration file")
    parser.add_argument("--list-source-tables", action="store_true", help="List all source tables configured in this service")
    parser.add_argument("--add-source-table", help="Add single table to service (format: schema.table)")
    parser.add_argument("--add-source-tables", nargs='+', help="Add multiple tables to service (space-separated, format: schema.table schema.table)")
    parser.add_argument("--remove-table", help="Remove table from service (format: schema.table or just table)")
    parser.add_argument("--inspect", action="store_true", help="Inspect database schema and list available tables (auto-detects MSSQL/PostgreSQL from service)")

    parser.add_argument("--schema", help="Database schema to inspect or filter (for --generate-validation: required if --all not used)")
    parser.add_argument("--save", action="store_true", help="Save detailed table schemas to YAML")
    parser.add_argument("--generate-validation", action="store_true", help="Generate JSON Schema for service YAML validation from database")
    parser.add_argument("--validate-hierarchy", action="store_true", help="Validate hierarchical inheritance (no duplicate values)")
    parser.add_argument("--validate-config", action="store_true", help="Comprehensive validation: required fields + hierarchy + pipeline readiness")
    parser.add_argument("--all", action="store_true", help="Process all schemas (required with --generate-validation if --schema not specified)")
    parser.add_argument("--env", default="nonprod", help="Environment for MSSQL inspection (default: nonprod)")
    parser.add_argument("--primary-key", help="Primary key column name (optional - auto-detected from schema)")
    parser.add_argument("--ignore-columns", action="append", help="Column to ignore (format: schema.table.column, can be specified multiple times)")
    parser.add_argument("--track-columns", action="append", help="Column to explicitly track (format: schema.table.column, can be specified multiple times)")
    
    # Legacy args (for backward compatibility)
    parser.add_argument("--source", help="Source service name (legacy)")
    parser.add_argument("--sink", help="Sink service name (legacy)")
    parser.add_argument("--source-schema", help="Source schema name (legacy)")
    parser.add_argument("--sink-schema", help="Sink schema name (legacy)")
    parser.add_argument("--source-table", help="Source table name (legacy)")
    parser.add_argument("--sink-table", help="Sink table name (legacy)")
    
    args = parser.parse_args()
    
    # Auto-detect service if not specified and only one service exists
    if not args.service:
        from cdc_generator.helpers.service_config import get_project_root
        services_dir = get_project_root() / 'services'
        
        if services_dir.exists():
            service_files = list(services_dir.glob('*.yaml'))
            if len(service_files) == 1:
                args.service = service_files[0].stem
                print_info(f"Auto-detected service: {args.service}")
            elif len(service_files) > 1 and not args.create_service:
                # Only require --service if there are multiple services and we're not in create mode
                available = ', '.join([f.stem for f in sorted(service_files)])
                print_error(f"❌ Multiple services found: {available}")
                print_error("Please specify --service <name>")
                return 1
    
    # Handle list-source-tables operation
    if args.list_source_tables:
        if not args.service:
            print_error("❌ Error: --service is required for --list-source-tables")
            return 1
        
        from cdc_generator.helpers.service_config import load_service_config
        
        try:
            config = load_service_config(args.service)
            
            # Try new hierarchical format first (shared.source_tables)
            source_tables: List[Dict[str, Any]] = config.get('shared', {}).get('source_tables', [])
            
            # If not found, try old flat format (source.tables)
            if not source_tables:
                old_tables: Dict[str, Any] = config.get('source', {}).get('tables', {})
                if old_tables:
                    # Convert old format to display
                    print_header(f"Source Tables in '{args.service}'")
                    
                    # Group by schema
                    schema_tables: Dict[str, List[Tuple[str, Any]]] = {}
                    for table_key, table_props in sorted(old_tables.items()):
                        key_str = str(table_key)
                        if '.' in key_str:
                            schema, table = key_str.split('.', 1)
                        else:
                            schema = 'dbo'
                            table = key_str
                        
                        if schema not in schema_tables:
                            schema_tables[schema] = []
                        schema_tables[schema].append((table, table_props))
                    
                    table_count = 0
                    for schema_name in sorted(schema_tables.keys()):
                        print(f"{Colors.BOLD}{Colors.BLUE}{schema_name}{Colors.RESET}")
                        
                        for table_name, table_properties in schema_tables[schema_name]:
                            table_count += 1
                            pk: str = ''
                            if isinstance(table_properties, dict):
                                pk_value = cast(str, table_properties.get('primary_key', '')) # type: ignore
                                pk = pk_value if pk_value else ''
                            
                            # Table name with optional PK
                            if pk:
                                print(f"  {Colors.CYAN}{table_name}{Colors.RESET} {Colors.DIM}(PK: {pk}){Colors.RESET}")
                            else:
                                print(f"  {Colors.CYAN}{table_name}{Colors.RESET}")
                        
                        print()  # Blank line between schemas
                    
                    print(f"{Colors.DIM}Total: {table_count} table(s) across {len(schema_tables)} schema(s){Colors.RESET}\n")
                    return 0
            
            if not source_tables:
                print_warning(f"No source tables configured in service '{args.service}'")
                print_info(f"Add tables with: cdc manage-service --service {args.service} --add-source-table <schema.table>")
                return 0
            
            print_header(f"Source Tables in '{args.service}'")
            
            table_count = 0
            # Group by schema
            for schema_group in source_tables:
                schema_name = schema_group.get('schema')
                tables = schema_group.get('tables', [])
                
                if tables:
                    print(f"{Colors.BOLD}{Colors.BLUE}{schema_name}{Colors.RESET}")
                    for table in tables:
                        table_count += 1
                        if isinstance(table, str):
                            print(f"  {Colors.CYAN}{table}{Colors.RESET}")
                        else:
                            table_name = table.get('name', 'Unknown')
                            pk = table.get('primary_key')
                            ignore_cols = table.get('ignore_columns', [])
                            track_cols = table.get('track_columns', [])
                            
                            # Table name with optional PK
                            if pk:
                                print(f"  {Colors.CYAN}{table_name}{Colors.RESET} {Colors.DIM}(PK: {pk}){Colors.RESET}")
                            else:
                                print(f"  {Colors.CYAN}{table_name}{Colors.RESET}")
                            
                            # Show ignore columns if present
                            if ignore_cols:
                                print(f"    {Colors.DIM}ignore:{Colors.RESET}")
                                for col in ignore_cols:
                                    print(f"      {Colors.RED}{col}{Colors.RESET}")
                            
                            # Show track columns if present
                            if track_cols:
                                print(f"    {Colors.DIM}track:{Colors.RESET}")
                                for col in track_cols:
                                    print(f"      {Colors.OKGREEN}{col}{Colors.RESET}")
                    
                    print()  # Blank line between schemas
            
            print(f"{Colors.DIM}Total: {table_count} table(s) across {len(source_tables)} schema(s){Colors.RESET}\n")
            return 0
            
        except FileNotFoundError as e:
            print_error(f"Service not found: {e}")
            return 1
        except Exception as e:
            print_error(f"Failed to list source tables: {e}")
            return 1
    
    # Handle create-service operation
    if args.create_service:
        if not args.service:
            print("❌ Error: --service is required for --create-service")
            return 1
        
        # Check if service already exists
        from cdc_generator.helpers.service_config import get_project_root
        services_dir = get_project_root() / 'services'
        service_file = services_dir / f'{args.service}.yaml'
        
        if service_file.exists():
            print_error(f"❌ Service '{args.service}' already exists: {service_file}")
            print_info(f"To modify it, edit the file directly or use:")
            print_info(f"  cdc manage-service --service {args.service} --add-source-table <schema.table>")
            return 1
        
        # Auto-detect server-group from server_group.yaml
        server_group = None
        server_groups_file = get_project_root() / 'server_group.yaml'
        
        # Also collect all defined services to show what's available
        defined_services: set[str] = set()
        existing_services: set[str] = set()
        
        if server_groups_file.exists() and yaml is not None:
            with open(server_groups_file) as f:
                server_groups_data = yaml.safe_load(f)
                for sg_name, sg in server_groups_data.get('server_group', {}).items():
                    # For db-per-tenant: group name IS the service name
                    if sg.get('pattern') == 'db-per-tenant':
                        defined_services.add(sg_name)
                        if sg_name == args.service:
                            server_group = sg_name
                            print_info(f"Auto-detected server group: {server_group}")
                    # For db-shared: collect all database service names
                    elif sg.get('pattern') == 'db-shared':
                        for db in sg.get('databases', []):
                            svc = db.get('service')
                            if svc:
                                defined_services.add(svc)
                                if svc == args.service:
                                    server_group = sg_name
                                    print_info(f"Auto-detected server group: {server_group} (from database {db.get('name')})")
        
        # Check which services already have config files
        if services_dir.exists():
            for svc_file in services_dir.glob('*.yaml'):
                existing_services.add(svc_file.stem)
        
        # Show helpful info about service status
        if defined_services:
            missing_services = defined_services - existing_services
            if not missing_services:
                print_warning("⚠️  All services defined in server_group.yaml already have configuration files")
                print_info(f"Existing services: {', '.join(sorted(existing_services))}")
            elif args.service not in defined_services:
                print_error(f"❌ Service '{args.service}' not found in server_group.yaml")
                if missing_services:
                    print_info(f"Services defined but not yet created: {', '.join(sorted(missing_services))}")
                    print_info(f"Did you mean one of these? {', '.join(sorted(missing_services))}")
                return 1
        
        if not server_group:
            print_error(f"❌ Could not find server group for service '{args.service}'")
            print_error(f"Add service mapping to server_group.yaml")
            return 1
            
        create_service(args.service, server_group)
        return 0
    
    # Handle validate-config operation (comprehensive)
    if args.service and args.validate_config:
        if validate_service_config(args.service):
            return 0
        return 1
    
    # Handle validate-hierarchy operation
    if args.service and args.validate_hierarchy:
        if validate_hierarchy_no_duplicates(args.service):
            return 0
        return 1
    
    # Handle generate-validation operation
    if args.service and args.generate_validation:
        # Require either --all or explicit --schema
        if not args.all and not args.schema:
            print_error("Error: --generate-validation requires either --all (for all schemas) or --schema <name> (for specific schema)")
            return 1
        
        schema_filter = None if args.all else args.schema
        if generate_service_validation_schema(args.service, args.env, schema_filter):
            return 0
        return 1
    
    # Handle inspect operation (auto-detect database type)
    if args.service and args.inspect:
        # Load service config to detect database type
        from cdc_generator.helpers.service_config import load_service_config, get_project_root
        config: dict[str, Any] = load_service_config(args.service)  
        
        # Try to get database type from source.type first (legacy format)
        db_type = config.get('source', {}).get('type')
        server_group: str | None = config.get('server_group')
        
        # If not found, try from server group (new format)
        if not db_type and server_group:
            server_groups_file = get_project_root() / 'server_group.yaml'
            if server_groups_file.exists() and yaml is not None:
                with open(server_groups_file) as f:
                    server_groups_data = yaml.safe_load(f)
                    server_group_dict = server_groups_data.get('server_group', {})
                    if server_group in server_group_dict:
                        sg = server_group_dict[server_group]
                        db_type = sg.get('server', {}).get('type')
        
        if not db_type:
            print_error(f"Could not determine database type for service '{args.service}'")
            print_error(f"Check that source.type is set or server_group exists in server_group.yaml")
            return 1
        
        # Require either --all or explicit --schema
        if not args.all and not args.schema:
            print_error(f"Error: --inspect requires either --all (for all schemas) or --schema <name> (for specific schema)")
            return 1
        
        # Get allowed schemas from server_group.yaml (only for services with server_group)
        allowed_schemas = None
        server_group_found = False
        
        if server_group:
            server_groups_file = get_project_root() / 'server_group.yaml'
            
            if server_groups_file.exists() and yaml is not None:
                with open(server_groups_file) as f:
                    server_groups_data = yaml.safe_load(f)
                    server_group_dict = server_groups_data.get('server_group', {})
                    if server_group in server_group_dict:
                        sg = server_group_dict[server_group]
                        server_group_found = True
                            
                        # For db-per-tenant: get schemas from database_ref
                        if sg.get('pattern') == 'db-per-tenant':
                            database_ref = sg.get('database_ref')
                            if not database_ref:
                                print_error(f"No database_ref defined for server group '{server_group}'")
                                return 1
                                
                            # Find the reference database
                                for db in sg.get('databases', []):
                                    if db.get('name') == database_ref:
                                        allowed_schemas = db.get('schemas', [])
                                        break
                                
                                if not allowed_schemas:
                                    print_error(f"Reference database '{database_ref}' not found in databases list for server group '{server_group}'")
                                    return 1
                            
                            # For db-shared: find database matching service name
                            elif sg.get('pattern') == 'db-shared':
                                for db in sg.get('databases', []):
                                    if db.get('service') == args.service:
                                        allowed_schemas = db.get('schemas', [])
                                        break
                                
                                if not allowed_schemas:
                                    print_error(f"No database with service='{args.service}' found in server group '{server_group}'")
                                    return 1
            
            # If server_group is set but not found in server_group.yaml
            if not server_group_found:
                # Check if we have source.type as fallback
                if config.get('source', {}).get('type'):
                    print_warning(f"Server group '{server_group}' not found in server_group.yaml, using source.type")
                    allowed_schemas = [args.schema] if args.schema else []
                else:
                    print_error(f"Server group '{server_group}' not found in server_group.yaml")
                    return 1
        # No server_group set - use any schema (services with direct source.type)
        else:
            allowed_schemas = [args.schema] if args.schema else []
        
        # Ensure allowed_schemas is always a list of strings (not None, not other types)
        if not isinstance(allowed_schemas, list):
            allowed_schemas = []
        allowed_schemas = cast(List[str], allowed_schemas)
        schema = args.schema  # None if --all, specific schema otherwise
        
        # Validate schema if explicitly provided
        if schema and schema not in allowed_schemas:
            print_error(f"Schema '{schema}' not allowed for service '{args.service}'")
            print_error(f"Allowed schemas: {', '.join(allowed_schemas)}")
            return 1
        
        schema_msg = f"schemas: {', '.join(allowed_schemas)}" if args.all else f"schema: {schema}"
        print_header(f"Inspecting {db_type.upper()} schema for {args.service} ({schema_msg})")
        
        # Call appropriate inspector based on database type
        if db_type == 'mssql':
            tables = inspect_mssql_schema(args.service, args.env)  
        elif db_type == 'postgres':
            tables = inspect_postgres_schema(args.service, args.env)  
        else:
            print_error(f"Unsupported database type: {db_type}")
            return 1
        
        if tables:
            # Filter by allowed schemas from server_group.yaml
            if args.all:
                # When --all, filter to only allowed schemas
                tables = [t for t in tables if t['TABLE_SCHEMA'] in allowed_schemas]  
            else:
                # When specific schema, filter to that schema only
                tables = [t for t in tables if t['TABLE_SCHEMA'] == schema]  
            
            if not tables:
                if args.all:
                    print_warning(f"No tables found in allowed schemas: {', '.join(allowed_schemas)}")
                else:
                    print_warning(f"No tables found in schema '{schema}'")
                return 1
            
            if args.save:
                # Save detailed schema using modular function
                if save_detailed_schema(args.service, args.env, schema, tables, db_type):  
                    return 0
                return 1
            else:
                # Just list tables
                print_success(f"Found {len(tables)} tables:\n")  
                current_schema = None
                for table in tables:  # type: ignore[union-attr]
                    tbl_schema = table['TABLE_SCHEMA']  
                    if tbl_schema != current_schema:
                        print(f"\n{Colors.CYAN}[{tbl_schema}]{Colors.RESET}")
                        current_schema = tbl_schema  
                    print(f"  {table['TABLE_NAME']} ({table['COLUMN_COUNT']} columns)")  
        return 0 if tables else 1
    
    # Handle add-source-tables (bulk) operation
    if args.service and args.add_source_tables:
        success_count = 0
        failed_count = 0
        
        for table_spec in args.add_source_tables:
            table_spec = table_spec.strip()
            if not table_spec:
                continue
            
            # Parse schema.table format
            if '.' in table_spec:
                schema, table = table_spec.split('.', 1)
            else:
                schema = args.schema if args.schema else 'dbo'
                table = table_spec
            
            # Parse column specs in format schema.table.column
            ignore_cols = None
            track_cols = None
            
            if args.ignore_columns:
                # Filter columns for this specific table
                table_prefix = f"{schema}.{table}."
                ignore_cols = [col.replace(table_prefix, '') for col in args.ignore_columns if col.startswith(table_prefix)]
            
            if args.track_columns:
                # Filter columns for this specific table
                table_prefix = f"{schema}.{table}."
                track_cols = [col.replace(table_prefix, '') for col in args.track_columns if col.startswith(table_prefix)]
            
            if add_table_to_service(args.service, schema, table, args.primary_key, ignore_cols or None, track_cols or None):
                success_count += 1
            else:
                failed_count += 1
        
        if success_count > 0:
            print_success(f"\\nAdded {success_count} table(s)")
            if failed_count > 0:
                print_warning(f"Failed to add {failed_count} table(s)")
            print_info("Run 'cdc generate' to update pipelines")
            return 0
        return 1
    
    # Handle add-source-table operation
    if args.service and args.add_source_table:
        # Parse schema.table format
        if '.' in args.add_source_table:
            schema, table = args.add_source_table.split('.', 1)
        else:
            # Use --schema flag or default to 'dbo'
            schema = args.schema if args.schema else 'dbo'
            table = args.add_source_table
        
        # Parse column specs in format schema.table.column
        ignore_cols = None
        track_cols = None
        
        if args.ignore_columns:
            table_prefix = f"{schema}.{table}."
            ignore_cols = [col.replace(table_prefix, '') for col in args.ignore_columns if col.startswith(table_prefix)]
        
        if args.track_columns:
            table_prefix = f"{schema}.{table}."
            track_cols = [col.replace(table_prefix, '') for col in args.track_columns if col.startswith(table_prefix)]
        
        if add_table_to_service(args.service, schema, table, args.primary_key, ignore_cols or None, track_cols or None):
            print_info("\nRun 'cdc generate' to update pipelines")
            return 0
        return 1
    
    # Handle remove-table operation
    if args.service and args.remove_table:
        # Parse schema.table format
        if '.' in args.remove_table:
            schema, table = args.remove_table.split('.', 1)
        else:
            # Use --schema flag or default to 'dbo'
            schema = args.schema if args.schema else 'dbo'
            table = args.remove_table
        
        if remove_table_from_service(args.service, schema, table):
            print_info("\nRun 'cdc generate' to update pipelines")
            return 0
        return 1
    
    # If no operation specified or service missing, show help
    if not args.service:
        # List available services
        from cdc_generator.helpers.service_config import get_project_root
        services_dir = get_project_root() / "services"
        if services_dir.exists():
            service_files = sorted(services_dir.glob("*.yaml"))
            if service_files:
                print_header("Available Services")
                for service_file in service_files:
                    service_name = service_file.stem
                    print(f"  • {service_name}")
                print()
        
        print_error("❌ Error: --service is required")
        print_info("Usage: cdc manage-service --service <name> [options]")
        print_info("Run 'cdc manage-service --help' for more information")
        return 1
    
    # Interactive mode (legacy workflow - delegates to interactive_mode module)
    return run_interactive_mode(args)


if __name__ == "__main__":
    sys.exit(main())