import argparse
import sys
import yaml
import ast
from pathlib import Path
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
import questionary

from .templates.registry import TemplateRegistry
from .config import ConfigFactory

console = Console()

def init_project(project_name: str, target_dir: Path, template: str = "standard", interactive: bool = False):
    """
    Scaffolds a new Theus project.
    """
    console.print(f"[bold green]🚀 Initializing Theus Project: {project_name}[/bold green]")
    
    # 1. Create Directories
    try:
        (target_dir / "src" / "processes").mkdir(parents=True, exist_ok=True)
        (target_dir / "workflows").mkdir(parents=True, exist_ok=True)
        (target_dir / "specs").mkdir(parents=True, exist_ok=True)
    except OSError as e:
        console.print(f"[bold red]❌ Error creating directories: {e}[/bold red]")
        sys.exit(1)

    # 2. Get Template Files
    try:
        files_to_create = TemplateRegistry.get_template(template)
    except ValueError as e:
        console.print(f"[bold red]❌ {e}[/bold red]")
        sys.exit(1)

    # 3. Write Files
    for rel_path, content in files_to_create.items():
        file_path = target_dir / rel_path
        if file_path.exists():
            console.print(f"   [yellow]⚠️  Skipping existing file: {rel_path}[/yellow]")
            continue
            
        # Ensure parent dir exists (for template files in subdir)
        file_path.parent.mkdir(parents=True, exist_ok=True)
            
        with open(file_path, "w", encoding="utf-8") as f:
            f.write(content)
        console.print(f"   [green]✅ Created {rel_path}[/green]")

    console.print(f"\n[bold blue]🎉 Project created successfully using '{template}' template![/bold blue]")
    console.print("\nNext steps:")
    if project_name != ".":
        console.print(f"  cd {project_name}")
    console.print("  pip install -r requirements.txt (if you have one)")
    console.print("  python main.py")

def gen_spec(target_dir: Path = Path.cwd()):
    """
    Scans src/processes/*.py and generates missing rules in specs/audit_recipe.yaml
    """
    console.print("[cyan]🔍 Scanning processes for Audit Spec generation...[/cyan]")
    processes_dir = target_dir / "src" / "processes"
    recipe_path = target_dir / "specs" / "audit_recipe.yaml"
    
    if not processes_dir.exists():
        console.print(f"[bold red]❌ Processes directory not found: {processes_dir}[/bold red]")
        return

    # 1. Parse Python Files
    discovered_recipes = {}
    
    for py_file in processes_dir.glob("*.py"):
        if py_file.name.startswith("__"): continue
        
        with open(py_file, "r", encoding="utf-8") as f:
            tree = ast.parse(f.read())
            
        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                # Check for @process decorator
                is_process = any(
                    (isinstance(d, ast.Call) and getattr(d.func, 'id', '') == 'process') 
                    or (isinstance(d, ast.Name) and d.id == 'process')
                    for d in node.decorator_list
                )
                
                if is_process:
                    # Extracts inputs/outputs/side_effects/errors from decorator
                    skeleton = {
                        "inputs": [{"field": "TODO_FIELD", "level": "I", "min": 0}], # Default: Ignore until configured
                        "outputs": [{"field": "TODO_FIELD", "level": "I", "threshold": 3}],
                        "side_effects": [], # New V2 Feature
                        "errors": []        # New V2 Feature
                    }

                    # Heuristic: Parse Decorator Kwargs
                    for d in node.decorator_list:
                        if isinstance(d, ast.Call) and getattr(d.func, 'id', '') == 'process':
                            for kw in d.keywords:
                                if kw.arg in ('inputs', 'outputs'):
                                    # We keep the generic TODO skeleton for I/O rules as they are complex rule objects
                                    pass
                                elif kw.arg == 'side_effects':
                                    try:
                                        skeleton['side_effects'] = ast.literal_eval(kw.value)
                                    except:
                                        skeleton['side_effects'] = ["__DYNAMIC__"]
                                elif kw.arg == 'errors':
                                    try:
                                        skeleton['errors'] = ast.literal_eval(kw.value)
                                    except:
                                        skeleton['errors'] = ["__DYNAMIC__"]

                    process_name = node.name
                    discovered_recipes[process_name] = skeleton
                    console.print(f"   found process: [bold]{process_name}[/bold]")

    if not discovered_recipes:
        console.print("[yellow]⚠️ No processes found.[/yellow]")
        return

    # 2. Merge with existing YAML
    existing_data = {}
    if recipe_path.exists():
        with open(recipe_path, 'r') as f:
            existing_data = yaml.safe_load(f) or {}

    if 'process_recipes' not in existing_data:
        existing_data['process_recipes'] = {}

    changes_made = False
    for name, skeleton in discovered_recipes.items():
        if name not in existing_data['process_recipes']:
            existing_data['process_recipes'][name] = skeleton
            changes_made = True
            console.print(f"   [green]➕ Added skeleton for {name}[/green]")

    if changes_made:
        with open(recipe_path, 'w', encoding='utf-8') as f:
            yaml.dump(existing_data, f, sort_keys=False)
        console.print(f"[green]✅ Updated {recipe_path}[/green]")
    else:
        console.print("✨ No new processes to add.")

def inspect_process(process_name: str, target_dir: Path = Path.cwd()):
    """
    Displays the effective audit rules for a process using Rich Tables.
    """
    recipe_path = target_dir / "specs" / "audit_recipe.yaml"
    if not recipe_path.exists():
        console.print(f"[bold red]❌ No audit recipe found at {recipe_path}[/bold red]")
        return

    try:
        recipe_book = ConfigFactory.load_recipe(str(recipe_path))
        recipe = recipe_book.definitions.get(process_name)
        
        if not recipe:
            console.print(f"[bold red]❌ Process '{process_name}' not found in Audit Recipe.[/bold red]")
            return
            
        console.print(Panel(f"[bold cyan]🔍 Audit Inspector: {process_name}[/bold cyan]"))
        
        def _get_condition_str(rule):
            # Helper to format condition from dict
            conds = []
            for k, v in rule.items():
                if k in ('field', 'level', 'message', 'min_threshold', 'max_threshold', 'reset_on_success'): continue
                conds.append(f"{k}={v}")
            return ", ".join(conds)

        # Inputs Table
        table_in = Table(title="📥 INPUTS", show_lines=True)
        table_in.add_column("Field", style="cyan")
        table_in.add_column("Condition", style="magenta")
        table_in.add_column("Level", style="yellow")
        
        for r in recipe.get('inputs', []):
            field = r.get('field', 'Unknown')
            cond = _get_condition_str(r)
            level = r.get('level', 'I')
            table_in.add_row(field, cond, str(level))
        console.print(table_in)
            
        # Outputs Table
        table_out = Table(title="📤 OUTPUTS", show_lines=True)
        table_out.add_column("Field", style="cyan")
        table_out.add_column("Condition", style="magenta")
        table_out.add_column("Level", style="yellow")
        
        for r in recipe.get('outputs', []):
            field = r.get('field', 'Unknown')
            cond = _get_condition_str(r)
            level = r.get('level', 'I')
            table_out.add_row(field, cond, str(level))
        console.print(table_out)

        console.print("\n[bold]⚡ SIDE EFFECTS:[/bold]")
        side_effects = recipe.get('side_effects', [])
        if side_effects:
            for s in side_effects:
                console.print(f"   - {s}")
        else:
            console.print("   (None declared)")

        console.print("\n[bold]🚫 EXPECTED ERRORS:[/bold]")
        errors = recipe.get('errors', [])
        if errors:
            for e in errors:
                console.print(f"   - {e}")
        else:
            console.print("   (None declared)")
            
    except Exception as e:
        console.print(f"[bold red]❌ Error loading recipe: {e}[/bold red]")

def main():
    parser = argparse.ArgumentParser(description="Theus SDK CLI - Manage your Process-Oriented projects.")
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # Command: init
    parser_init = subparsers.add_parser("init", help="Initialize a new Theus project.")
    parser_init.add_argument("name", nargs="?", help="Name of the project (optional for interactive).")
    parser_init.add_argument("--template", help="Template to use (minimal, standard, agent).")
    parser_init.add_argument("--quiet", action="store_true", help="Non-interactive mode.")

    # Command: check (New V3.1)
    parser_check = subparsers.add_parser("check", help="Run POP Static Analysis (Linter).")
    parser_check.add_argument("target", nargs="?", default=".", help="Directory to check.")

    # Command: audit
    parser_audit = subparsers.add_parser("audit", help="Audit tools.")
    audit_subs = parser_audit.add_subparsers(dest="audit_command")
    
    # audit gen-spec
    parser_gen = audit_subs.add_parser("gen-spec", help="Generate/Update audit_recipe.yaml from code.")

    # audit inspect
    parser_inspect = audit_subs.add_parser("inspect", help="Inspect effective rules for a process.")
    parser_inspect.add_argument("process_name", help="Name of the process to inspect.")

    # Command: schema
    parser_schema = subparsers.add_parser("schema", help="Data Schema tools.")
    schema_subs = parser_schema.add_subparsers(dest="schema_command")
    
    # schema gen
    parser_schema_gen = schema_subs.add_parser("gen", help="Generate context_schema.yaml from Python Definitions.")
    parser_schema_gen.add_argument("--context-file", default="src/context.py", help="Path to Python context definition (default: src/context.py)")

    # schema code
    parser_schema_code = schema_subs.add_parser("code", help="Generate src/context.py from YAML Schema.")
    parser_schema_code.add_argument("--schema-file", default="specs/context_schema.yaml", help="Path to YAML schema (default: specs/context_schema.yaml)")
    parser_schema_code.add_argument("--out-file", default="src/context.py", help="Output Python file path (default: src/context.py)")

    args = parser.parse_args()

    if args.command == "init":
        # Interactive Mode Logic
        if not args.name and not args.quiet:
            args.name = questionary.text("Project Name:", default="my-theus-app").ask()
        
        project_name = args.name or "."
        
        if not args.template and not args.quiet:
            args.template = questionary.select(
                "Choose a template:",
                choices=[
                    questionary.Choice(title=f"{name: <10} - {desc}", value=name)
                    for name, desc in TemplateRegistry.list_templates_details()
                ],
                default="standard"
            ).ask()
            
        template = args.template or "standard"
        
        if project_name == ".":
            target_path = Path.cwd()
            project_name = target_path.name
        else:
            target_path = Path.cwd() / project_name
            if target_path.exists() and any(target_path.iterdir()):
                console.print(f"[bold red]❌ Directory '{project_name}' exists and is not empty.[/bold red]")
                sys.exit(1)
            target_path.mkdir(exist_ok=True)
            
        init_project(project_name, target_path, template=template)
    
    elif args.command == "check":
        from .linter import run_lint
        target = Path(args.target)
        if not target.exists():
             console.print(f"[bold red]❌ Target path does not exist: {target}[/bold red]")
             sys.exit(1)
        success = run_lint(target)
        if not success:
            sys.exit(1)
        
    elif args.command == "audit":
        if args.audit_command == "gen-spec":
            gen_spec()
        elif args.audit_command == "inspect":
            inspect_process(args.process_name)
            
    elif args.command == "schema":
        if args.schema_command == "gen":
            from .schema_gen import generate_schema_from_file
            console.print(f"[cyan]🔍 Scanning context definition: {args.context_file}[/cyan]")
            try:
                schema_dict = generate_schema_from_file(args.context_file)
                output_path = Path("specs/context_schema.yaml")
                output_path.parent.mkdir(exist_ok=True)
                
                with open(output_path, "w", encoding="utf-8") as f:
                    yaml.dump(schema_dict, f, sort_keys=False)
                    
                console.print(f"[green]✅ Generated schema at: {output_path}[/green]")
                console.print(yaml.dump(schema_dict, sort_keys=False))
                
            except Exception as e:
                console.print(f"[bold red]❌ Failed to generate schema: {e}[/bold red]")

        elif args.schema_command == "code":
            from .schema_gen import generate_code_from_schema
            console.print(f"[cyan]🏗️  Generating Context Code from: {args.schema_file}[/cyan]")
            try:
                code_content = generate_code_from_schema(args.schema_file)
                output_path = Path(args.out_file)
                output_path.parent.mkdir(exist_ok=True)
                
                with open(output_path, "w", encoding="utf-8") as f:
                    f.write(code_content)
                    
                console.print(f"[green]✅ Generated Python Context at: {output_path}[/green]")
                
            except Exception as e:
                console.print(f"[bold red]❌ Failed to generate code: {e}[/bold red]")

    else:
        parser.print_help()

if __name__ == "__main__":
    main()
