import typer
import subprocess
import shutil
import sys
from typing import List
from pathlib import Path
from rich.console import Console
from rich.table import Table
from rich.prompt import Confirm, Prompt
from rich.progress import Progress, SpinnerColumn, TextColumn
from packaging.version import parse as parse_version

from dep_shield.parsers.npm import NPMParser
from dep_shield.parsers.pypi import PyPIParser
from dep_shield.providers.osv import OSVProvider
from dep_shield.models import ScanResult, Severity, Dependency, Ecosystem
from dep_shield.resolver import get_latest_version 

app = typer.Typer()
console = Console()

def get_parser(file_path: str):
    if file_path.endswith("package.json"):
        return NPMParser()
    if file_path.endswith("requirements.txt"):
        return PyPIParser()
    return None

@app.command()
def scan(
    target: str = typer.Argument(..., help="Path to manifest file (package.json or requirements.txt)"),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON for CI/CD pipelines"),
    fail_on_high: bool = typer.Option(False, "--fail-on-high", help="Exit with error code if High/Critical found")
):
    """
    Scans a dependency manifest for known vulnerabilities.
    """
    path = Path(target)
    if not path.exists():
        console.print(f"[bold red]Error:[/bold red] File {target} not found.")
        raise typer.Exit(code=1)

    parser = get_parser(target)
    if not parser:
        console.print(f"[bold red]Error:[/bold red] No parser available for {target}")
        raise typer.Exit(code=1)

    try:
        dependencies = parser.parse(str(path))
    except Exception as e:
        console.print(f"[bold red]Parsing Failed:[/bold red] {e}")
        raise typer.Exit(code=1)

    if not dependencies:
        console.print("[yellow]No dependencies found to scan.[/yellow]")
        raise typer.Exit()

    provider = OSVProvider()
    results: List[ScanResult] = []
    
    if not json_output:
        console.print(f"[bold blue]Dep-Shield[/bold blue] scanning [bold green]{len(dependencies)}[/bold green] packages from {target}...")

    with Progress(
        SpinnerColumn(),
        TextColumn("[progress.description]{task.description}"),
        transient=True,
        disable=json_output
    ) as progress:
        task = progress.add_task("Querying OSV Database...", total=len(dependencies))
        
        for dep in dependencies:
            vulns = provider.check(dep)
            if vulns:
                results.append(ScanResult(dependency=dep, vulnerabilities=vulns))
            progress.advance(task)

    if json_output:
        output = [res.dict() for res in results]
        print(json.dumps(output, indent=2))
    else:
        if not results:
            console.print(f"\n[bold green]✓ No known vulnerabilities found in {target}.[/bold green]")
        else:
            table = Table(title=f"Vulnerabilities Found in {target}")
            table.add_column("Package", style="cyan")
            table.add_column("Version", style="magenta")
            table.add_column("Severity", justify="center")
            table.add_column("ID", style="dim")
            table.add_column("Summary")

            found_high_crit = False

            for res in results:
                for vuln in res.vulnerabilities:
                    sev_style = "green"
                    if vuln.severity in [Severity.HIGH, Severity.CRITICAL]:
                        sev_style = "bold red"
                        found_high_crit = True
                    elif vuln.severity == Severity.MODERATE:
                        sev_style = "yellow"

                    table.add_row(
                        res.dependency.name,
                        res.dependency.version,
                        f"[{sev_style}]{vuln.severity}[/{sev_style}]",
                        vuln.id,
                        vuln.summary
                    )

            console.print("\n")
            console.print(table)
            
            if fail_on_high and found_high_crit:
                console.print("\n[bold red]Critical security threshold exceeded. Process failed.[/bold red]")
                raise typer.Exit(code=1)

@app.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
def guard(ctx: typer.Context):
    """
    Interceptor for npm/pip/pnpm commands. 
    Usage: dep-shield guard npm install react
    """
    raw_args = ctx.args
    # [Argument cleaning logic from previous steps...]
    args = [arg for arg in raw_args if arg != "guard"]
    
    if not args or not shutil.which(args[0]):
        # Fallback if something is wrong
        if args: subprocess.call(args)
        return

    cmd_base = args[0]
    sub_cmd = args[1] if len(args) > 1 else ""
    is_install = sub_cmd in ["install", "i", "add"]
    
    # 1. Parsing requested packages
    requested_packages = []
    if is_install:
        for arg in args[2:]:
            if not arg.startswith("-"):
                requested_packages.append(arg)

    if not requested_packages:
        subprocess.call(args)
        raise typer.Exit()

    # 2. Ecosystem & Scanning
    ecosystem = Ecosystem.NPM
    if "pip" in cmd_base or "python" in cmd_base:
        ecosystem = Ecosystem.PYPI
    
    console.print(f"[bold blue]Dep-Shield Guard[/bold blue] analyzing {len(requested_packages)} packages...")
    
    provider = OSVProvider()
    results: List[ScanResult] = []
    
    suggested_fixes = {}

    with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress:
        task = progress.add_task("Scanning...", total=len(requested_packages))
        
        for pkg_str in requested_packages:
            # Parse version (Simplistic)
            version = "latest"
            clean_name = pkg_str
            
            # Extract name/version from input string
            if ecosystem == Ecosystem.NPM:
                if "@" in pkg_str and not pkg_str.startswith("@"):
                     clean_name, version = pkg_str.split("@", 1)
            elif ecosystem == Ecosystem.PYPI:
                if "==" in pkg_str:
                     clean_name, version = pkg_str.split("==", 1)

            # Resolve 'latest' to actual number
            if version == "latest":
                version = get_latest_version(clean_name, ecosystem)

            dep = Dependency(name=clean_name, version=version, ecosystem=ecosystem, source_file="CLI")
            vulns = provider.check(dep)
            
            if vulns:
                # logic to find the highest "fixed_in" version
                safe_ver = None
                for v in vulns:
                    if v.fixed_in:
                        # If we have a fix, try to see if it's newer than what we have
                        # (Simple string check, ideally use packaging.version)
                        if not safe_ver or parse_version(v.fixed_in) > parse_version(safe_ver):
                            safe_ver = v.fixed_in
                
                if safe_ver:
                    suggested_fixes[clean_name] = safe_ver

                results.append(ScanResult(dependency=dep, vulnerabilities=vulns))
            
            progress.advance(task)

    # 3. Report & Recommend
    if results:
        console.print("\n[bold red]⚠️  Security Risks Detected![/bold red]")
        table = Table(show_header=True, header_style="bold red")
        table.add_column("Package")
        table.add_column("Current")
        table.add_column("Severity")
        table.add_column("Recommendation", style="green")

        for res in results:
            recommendation = "No fix available"
            if res.dependency.name in suggested_fixes:
                recommendation = f"Upgrade to {suggested_fixes[res.dependency.name]}"
            
            # Just show the highest severity for brevity in the table
            max_sev = res.vulnerabilities[0].severity.value
            
            table.add_row(
                res.dependency.name,
                res.dependency.version,
                max_sev,
                recommendation
            )
        console.print(table)
        console.print("\n")

        # 4. Interactive Fix
        if suggested_fixes:
            if Confirm.ask("[bold green]Do you want to apply the recommended fixes automatically?[/bold green]"):
                final_packages = []
                for pkg in requested_packages:
                    # Check if this package name matches a fix
                    # (Naive string match, needs to be robust for @scope/pkg)
                    pkg_name_only = pkg.split("@")[0] if ecosystem==Ecosystem.NPM else pkg.split("==")[0]
                    
                    if pkg_name_only in suggested_fixes:
                        fix_ver = suggested_fixes[pkg_name_only]
                        console.print(f"[green]âœ“ Switching {pkg_name_only} -> {fix_ver}[/green]")
                        
                        if ecosystem == Ecosystem.NPM:
                            final_packages.append(f"{pkg_name_only}@{fix_ver}")
                        else:
                            final_packages.append(f"{pkg_name_only}=={fix_ver}")
                    else:
                        final_packages.append(pkg)
                
                # Execute with NEW list
                final_cmd = [cmd_base, sub_cmd] + [arg for arg in args[2:] if arg.startswith("-")] + final_packages
                console.print(f"[dim]Running safer command...[/dim]")
                subprocess.call(final_cmd)
                raise typer.Exit()

        # If no fixes or user said no, ask to proceed or abort
        if not Confirm.ask("Proceed with vulnerable installation?"):
             console.print("[red]Aborted.[/red]")
             raise typer.Exit(1)

    # 5. Passthrough (No issues found, or user chose to ignore)
    subprocess.call(args)

@app.command()
def install_hooks():
    """
    Prints the aliases needed to enable Dep-Shield.
    """
    console.print("[bold]Copy these lines to your .bashrc / .zshrc:[/bold]\n")
    console.print('alias npm="dep-shield guard npm"')
    console.print('alias pip="dep-shield guard pip"')
    console.print('alias pip3="dep-shield guard pip3"')
    console.print('alias pnpm="dep-shield guard pnpm"')
    console.print("\n[dim]Then run: source ~/.bashrc[/dim]")

if __name__ == "__main__":
    app()