"""Command-line interface for Prismor security scanning tool."""

import sys
import json
import os
import click
import threading
import time
from typing import Optional
from .api import PrismorClient, PrismorAPIError, parse_github_repo


def print_success(message: str):
    """Print success message in green."""
    click.secho(f"✓ {message}", fg="green")


def print_error(message: str):
    """Print error message in red."""
    click.secho(f"✗ {message}", fg="red", err=True)


def print_info(message: str):
    """Print info message in blue."""
    click.secho(f"ℹ {message}", fg="blue")


def print_warning(message: str):
    """Print warning message in yellow."""
    click.secho(f"⚠ {message}", fg="yellow")


class Spinner:
    """Simple spinner for showing loading state."""
    
    def __init__(self, message: str = "Processing"):
        self.message = message
        self.spinner_chars = "|/-\\"
        self.spinner_index = 0
        self.running = False
        self.thread = None
    
    def _spin(self):
        """Internal spinner loop."""
        while self.running:
            char = self.spinner_chars[self.spinner_index % len(self.spinner_chars)]
            sys.stdout.write(f"\r{char} {self.message}...")
            sys.stdout.flush()
            self.spinner_index += 1
            time.sleep(0.1)
    
    def start(self):
        """Start the spinner."""
        self.running = True
        self.thread = threading.Thread(target=self._spin, daemon=True)
        self.thread.start()
    
    def stop(self, message: str = None):
        """Stop the spinner and clear the line."""
        self.running = False
        if self.thread:
            self.thread.join(timeout=0.2)
        # Clear the spinner line
        sys.stdout.write("\r" + " " * (len(self.message) + 15) + "\r")
        sys.stdout.flush()
        if message:
            click.echo(message)


def should_show_spinner() -> bool:
    """Show spinners only in interactive terminals, never in CI logs."""
    # Explicit override
    if os.environ.get("PRISMOR_NO_SPINNER", "").strip().lower() in {"1", "true", "yes", "on"}:
        return False
    # CI environments (including GitHub Actions) should not render spinner frames
    if os.environ.get("GITHUB_ACTIONS", "").strip().lower() == "true":
        return False
    if os.environ.get("CI", "").strip().lower() == "true":
        return False
    # Spinner animation is safe only on a real TTY
    return sys.stdout.isatty()


def format_prompt_results(results: dict, repo: str):
    """Format results with an LLM prompt for fixing vulnerabilities."""
    click.echo(f"# Prismor Security Scan Results for {repo}\n")
    
    click.echo("## Scan Data\n")
    click.echo("```json")
    
    # Filter out SBOM to avoid huge output that might bloat LLM context window
    prompt_data = {k: v for k, v in results.items() if k != "scans"}
    if "scans" in results:
        prompt_data["scans"] = {}
        for s_type, s_data in results["scans"].items():
            if s_type == "sbom":
                prompt_data["scans"][s_type] = {"status": s_data.get("status"), "message": "SBOM data omitted for brevity"}
            else:
                prompt_data["scans"][s_type] = s_data
                
    click.echo(json.dumps(prompt_data, indent=2))
    click.echo("```\n")
    
    click.echo("## Instructions for AI Agent:")
    click.echo("1. Review the vulnerabilities and secrets detected above.")
    click.echo("2. For each finding, locate the vulnerable code in the repository.")
    click.echo("3. Provide the necessary code changes to fix the security issues.")
    click.echo("4. If applicable, explain the fix and how it resolves the vulnerability.")


def format_scan_results(results: dict, scan_type: str):
    """Format and display scan results."""
    click.echo("\n" + "=" * 60)
    click.secho(f"  Scan Results - {scan_type}", fg="cyan", bold=True)
    click.echo("=" * 60 + "\n")
    
    # Display repository information
    if "repository" in results:
        click.secho("Repository:", fg="yellow", bold=True)
        click.echo(f"  {results['repository']}\n")
    
    if "branch" in results:
        click.secho("Branch:", fg="yellow", bold=True)
        click.echo(f"  {results['branch']}\n")
    
    if "commit_sha" in results:
        click.secho("Commit SHA:", fg="yellow", bold=True)
        click.echo(f"  {results['commit_sha']}\n")
    
    # Display scan results for each scan type
    if "scans" in results:
        scans = results["scans"]
        
        # Vulnerability scan results
        if "vulnerability" in scans:
            vuln_scan = scans["vulnerability"]
            click.secho("Vulnerability Scan:", fg="yellow", bold=True)
            status_color = "green" if vuln_scan.get("status") in ["success", "completed"] else "red"
            click.secho(f"  Status: {vuln_scan.get('status', 'unknown')}", fg=status_color)
            
            if "scan_results" in vuln_scan:
                scan_data = vuln_scan["scan_results"]
                if "vulnerabilities" in scan_data or "Results" in scan_data:
                    vuln_data = scan_data.get("vulnerabilities", scan_data.get("Results", []))
                    if isinstance(vuln_data, list):
                        click.echo(f"  Vulnerabilities Found: {len(vuln_data)}")
                    else:
                        click.echo(f"  Vulnerabilities: Data available")
            
            if "public_url" in vuln_scan:
                click.echo(f"  Results URL: {vuln_scan['public_url']}")
            click.echo()
        
        # SBOM results
        if "sbom" in scans:
            sbom_scan = scans["sbom"]
            click.secho("SBOM Generation:", fg="yellow", bold=True)
            status_color = "green" if sbom_scan.get("status") in ["success", "completed"] else "red"
            click.secho(f"  Status: {sbom_scan.get('status', 'unknown')}", fg=status_color)
            
            if "sbom" in sbom_scan:
                sbom_data = sbom_scan["sbom"]
                if isinstance(sbom_data, list):
                    click.echo(f"  Artifacts Found: {len(sbom_data)}")
                else:
                    click.echo(f"  SBOM: Data available")
            
            if "public_url" in sbom_scan:
                click.echo(f"  Results URL: {sbom_scan['public_url']}")
            click.echo()
        
        # Secret scan results
        if "secret" in scans:
            secret_scan = scans["secret"]
            click.secho("Secret Detection:", fg="yellow", bold=True)
            status_color = "green" if secret_scan.get("status") in ["success", "completed"] else "red"
            click.secho(f"  Status: {secret_scan.get('status', 'unknown')}", fg=status_color)
            
            if "summary" in secret_scan:
                summary = secret_scan["summary"]
                if isinstance(summary, dict):
                    for key, value in summary.items():
                        click.echo(f"  {key}: {value}")
                else:
                    click.echo(f"  Summary: {summary}")
            
            if "public_url" in secret_scan:
                click.echo(f"  Results URL: {secret_scan['public_url']}")
            click.echo()
    
    # Display scan timestamp
    if "scanned_at" in results:
        click.secho("Scanned At:", fg="yellow", bold=True)
        click.echo(f"  {results['scanned_at']}\n")
    
    click.echo("=" * 60 + "\n")


@click.group(invoke_without_command=True)
@click.option(
    "--repo",
    "scan_repo",
    type=str,
    help="Repository to scan (username/repo, GitHub URL, SSH URL, etc.)"
)
@click.option("--scan", is_flag=True, help="Perform vulnerability scanning")
@click.option("--sbom", is_flag=True, help="Generate Software Bill of Materials")
@click.option("--detect-secret", is_flag=True, help="Detect secrets in repository")
@click.option("--fullscan", is_flag=True, help="Perform all scan types")
@click.option("--branch", type=str, help="Specific branch to scan (defaults to main/master)")
@click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
@click.option("--output", "-o", type=click.Path(), help="Save results to file (JSON format)")
@click.option("--quiet", "-q", is_flag=True, help="Minimal output (only errors and final results)")
@click.option("--action_id", type=str, help="GitHub Action Run ID associated with this scan")
@click.option("--prompt", is_flag=True, help="Output detailed scan results with an LLM prompt for fixing vulnerabilities")
@click.version_option(version="1.1.6", prog_name="prismor")
@click.pass_context
def cli(ctx, scan_repo: Optional[str], scan: bool, sbom: bool, detect_secret: bool, 
        fullscan: bool, branch: Optional[str], output_json: bool, output: Optional[str], quiet: bool, action_id: Optional[str], prompt: bool):
    """Prismor CLI - Security scanning tool for GitHub repositories.
    
    Examples:
        prismor --repo username/repo --scan
        prismor --repo username/repo --fullscan
        prismor --repo https://github.com/username/repo --detect-secret
        prismor --repo git@github.com:username/repo.git --sbom
        prismor --repo github.com/username/repo --fullscan --branch develop
        prismor status
        prismor repos
    """
    # If no command and no scan option, show help
    if ctx.invoked_subcommand is None and not scan_repo:
        click.echo(ctx.get_help())
        return
    
    # If scan option is provided, perform the scan
    if scan_repo:
        # Check if at least one scan type is selected
        if not any([scan, sbom, detect_secret, fullscan]):
            print_error("Please specify at least one scan type: --scan, --sbom, --detect-secret, or --fullscan")
            sys.exit(1)
        
        try:
            # Initialize API client
            if not quiet:
                print_info(f"Initializing Prismor scan for: {scan_repo}")
            client = PrismorClient()
            
            # Determine scan type for display
            scan_types = []
            if fullscan:
                scan_types.append("Full Scan (scan + SBOM + Secret Detection)")
            else:
                if scan:
                    scan_types.append("scan")
                if sbom:
                    scan_types.append("SBOM")
                if detect_secret:
                    scan_types.append("Secret Detection")
            
            if not quiet:
                print_info(f"Scan type: {', '.join(scan_types)}")
                if scan or fullscan:
                    print_info("Starting scan... (vulnerability scans run asynchronously and may take up to 10 minutes)")
                else:
                    print_info("Starting scan... (this may take a few minutes)")
            
            # Show loading spinner during scan (unless quiet mode)
            spinner = None
            if not quiet and not output_json and not output and should_show_spinner():
                spinner = Spinner("Scanning repository")
                spinner.start()
            
            try:
                # Perform scan
                results = client.scan(
                    repo=scan_repo,
                    scan=scan,
                    sbom=sbom,
                    detect_secret=detect_secret,
                    fullscan=fullscan,
                    branch=branch,
                    action_id=action_id
                )
                if spinner:
                    spinner.stop()
            except Exception as e:
                if spinner:
                    spinner.stop()
                raise e
            
            # Save to file if --output specified
            if output:
                try:
                    with open(output, 'w') as f:
                        json.dump(results, f, indent=2)
                    if not quiet:
                        print_success(f"Results saved to: {output}")
                except Exception as e:
                    print_error(f"Failed to save results to file: {str(e)}")
                    sys.exit(1)
            
            # Output results
            if output_json or output:
                if not output:  # Only print to stdout if not saving to file
                    click.echo(json.dumps(results, indent=2))
            elif prompt:
                format_prompt_results(results, scan_repo)
            else:
                if not quiet:
                    print_success("Scan completed successfully!")
                format_scan_results(results, ', '.join(scan_types))
                
                # Try to get repository ID and display dashboard link (unless quiet mode)
                if not quiet:
                    try:
                        # Extract repo name from scan input using the comprehensive parser
                        repo_name = parse_github_repo(scan_repo)
                        
                        # Get repository ID
                        repo_info = client.get_repository_by_name(repo_name)
                        if repo_info.get("success") and "repository" in repo_info:
                            repo_id = repo_info["repository"]["id"]
                            dashboard_url = f"{client.base_url}/repositories/{repo_id}"
                            
                            click.echo("\n" + "=" * 60)
                            click.secho("  - Dashboard Analysis", fg="cyan", bold=True)
                            click.echo("=" * 60)
                            click.secho(f" - View detailed analysis and insights:", fg="blue")
                            click.secho(f"   {dashboard_url}", fg="green", bold=True)
                            click.echo("\n The dashboard provides:")
                            click.echo("   • Interactive visualizations and charts")
                            click.echo("   • Historical vulnerability trends")
                            click.echo("   • Detailed security reports")
                            click.echo("   • Team collaboration features")
                            click.echo("   • Export capabilities")
                            click.echo("=" * 60 + "\n")
                            
                    except PrismorAPIError as e:
                        # Repository might not be found, continue without dashboard link
                        print_warning(f"Could not generate dashboard link: {str(e)}")
                    except Exception as e:
                        # Any other error, continue without dashboard link
                        print_warning(f"Could not generate dashboard link: {str(e)}")
            
        except PrismorAPIError as e:
            print_error(str(e))
            sys.exit(1)
        except Exception as e:
            print_error(f"Unexpected error: {str(e)}")
            sys.exit(1)


@cli.command()
def version():
    """Display the version of Prismor CLI."""
    click.echo("Prismor CLI v1.1.5")


@cli.command()
def config():
    """Display current configuration."""

    click.echo("\n" + "=" * 60)
    click.secho("  Prismor CLI Configuration", fg="cyan", bold=True)
    click.echo("=" * 60 + "\n")
    
    # Check API key
    api_key = os.environ.get("PRISMOR_API_KEY")
    if api_key:
        # Show only first and last 4 characters
        masked_key = f"{api_key[:4]}...{api_key[-4:]}" if len(api_key) > 8 else "***"
        print_success(f"PRISMOR_API_KEY: {masked_key}")
    else:
        print_error("PRISMOR_API_KEY: Not set")
        click.echo("\nPlease specify your API key. You can generate one for free at:")
        click.secho("  https://www.prismor.dev/cli", fg="cyan", underline=True)
        click.echo("\nTo set your API key, run:")
        click.echo("  export PRISMOR_API_KEY=your_api_key")
    
    # click.echo("\nAPI Endpoint: http://localhost:3000")
    click.echo("=" * 60 + "\n")


@cli.command()
def repos():
    """List your connected repositories."""
    try:
        client = PrismorClient()
        spinner = Spinner("Loading repositories") if should_show_spinner() else None
        if spinner:
            spinner.start()
        try:
            repos_data = client.get_repositories()
            if spinner:
                spinner.stop()
        except Exception as e:
            if spinner:
                spinner.stop()
            raise e
        
        click.echo("\n" + "=" * 60)
        click.secho("  Your Repositories", fg="cyan", bold=True)
        click.echo("=" * 60 + "\n")
        
        user_info = repos_data.get("user", {})
        if user_info:
            click.secho(f"User: {user_info.get('name', 'Unknown')} ({user_info.get('email', 'No email')})", fg="yellow")
            click.echo()
        
        repositories = repos_data.get("repositories", [])
        if repositories:
            for repo in repositories:
                click.secho(f"• {repo.get('name', 'Unknown')}", fg="green")
                click.echo(f"  URL: {repo.get('htmlUrl', 'No URL')}")
                click.echo(f"  Owner: {repo.get('githubOwner', 'Unknown')}")
                click.echo()
        else:
            print_warning("No repositories found. Connect repositories through the web interface.")
        
        click.echo("=" * 60 + "\n")
        
    except PrismorAPIError as e:
        print_error(str(e))
        sys.exit(1)
    except Exception as e:
        print_error(f"Unexpected error: {str(e)}")
        sys.exit(1)


@cli.command()
@click.argument("repo", type=str)
@click.option("--branch", type=str, help="Specific branch to scan (defaults to main)")
@click.option("--token", type=str, help="GitHub token (or set GITHUB_TOKEN env var)")
@click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
def start_scan(repo: str, branch: Optional[str], token: Optional[str], output_json: bool):
    """Start a vulnerability scan and return a job_id for status checking.
    
    REPO is the repository to scan (username/repo, GitHub URL, SSH URL, etc.)
    
    This command starts a scan asynchronously and returns immediately with a job_id.
    Use 'prismor scan-status <job_id>' to check when the scan completes.
    
    Note: Requires GitHub token. Set GITHUB_TOKEN environment variable or use --token option.
    
    Examples:
        prismor start-scan username/repo
        prismor start-scan https://github.com/username/repo --branch develop
        prismor start-scan username/repo --token ghp_xxxxx
        prismor start-scan username/repo --json
    """
    try:
        client = PrismorClient()
        print_info(f"Starting vulnerability scan for: {repo}")
        if branch:
            print_info(f"Branch: {branch}")
        
        spinner = Spinner("Starting scan") if should_show_spinner() else None
        if spinner:
            spinner.start()
        try:
            result = client.start_vulnerability_scan(repo, branch, token)
            if spinner:
                spinner.stop()
        except Exception as e:
            if spinner:
                spinner.stop()
            raise e
        
        if output_json:
            click.echo(json.dumps(result, indent=2))
        else:
            click.echo("\n" + "=" * 60)
            click.secho("  Scan Started", fg="cyan", bold=True)
            click.echo("=" * 60 + "\n")
            
            job_id = result.get("job_id")
            if job_id:
                print_success(f"Scan started successfully!")
                click.echo()
                click.secho(f"Job ID: {job_id}", fg="yellow", bold=True)
                click.echo()
                click.secho("Repository:", fg="yellow", bold=True)
                click.echo(f"  {result.get('repository', repo)}")
                click.echo()
                if "branch" in result:
                    click.secho("Branch:", fg="yellow", bold=True)
                    click.echo(f"  {result['branch']}")
                    click.echo()
                click.secho("Status:", fg="yellow", bold=True)
                click.echo(f"  {result.get('status', 'accepted')}")
                click.echo()
                click.secho("Next Steps:", fg="cyan", bold=True)
                click.echo(f"  Check scan status with:")
                click.secho(f"    prismor scan-status {job_id}", fg="green", bold=True)
                click.echo()
            else:
                print_error("Failed to get job_id from response")
                click.echo(json.dumps(result, indent=2))
            
            click.echo("=" * 60 + "\n")
            
    except PrismorAPIError as e:
        print_error(str(e))
        sys.exit(1)
    except Exception as e:
        print_error(f"Unexpected error: {str(e)}")
        sys.exit(1)


@cli.command()
@click.argument("job_id", type=str)
@click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
@click.option("--prompt", is_flag=True, help="Output detailed scan results with an LLM prompt for fixing vulnerabilities")
def scan_status(job_id: str, output_json: bool, prompt: bool):
    """Check the status of a vulnerability scan job.
    
    JOB_ID is the job ID returned when starting a scan.
    
    Examples:
        prismor scan-status a724a4663cda4bf087ad171683cb726d
        prismor scan-status 50cbe253e5634227b81fe744c2a0b3e7 --json
    """
    try:
        client = PrismorClient()
        print_info(f"Checking scan status for job: {job_id}")
        
        spinner = Spinner("Checking status") if should_show_spinner() else None
        if spinner:
            spinner.start()
        try:
            status_data = client.check_scan_status(job_id)
            if spinner:
                spinner.stop()
        except Exception as e:
            if spinner:
                spinner.stop()
            raise e
        
        if output_json:
            click.echo(json.dumps(status_data, indent=2))
        elif prompt and status_data.get("status") in {"completed", "success"}:
            repo_name = status_data.get("repository", "Unknown Repository")
            format_prompt_results(status_data.get("results", status_data), repo_name)
        else:
            click.echo("\n" + "=" * 60)
            click.secho("  Scan Status", fg="cyan", bold=True)
            click.echo("=" * 60 + "\n")
            
            click.secho(f"Job ID: {status_data.get('job_id', job_id)}", fg="yellow", bold=True)
            click.echo()
            
            status = status_data.get("status", "unknown")
            if status == "completed":
                print_success(f"Status: {status}")
                click.echo()
                
                if "repository" in status_data:
                    click.secho("Repository:", fg="yellow", bold=True)
                    click.echo(f"  {status_data['repository']}")
                    click.echo()
                
                if "branch" in status_data:
                    click.secho("Branch:", fg="yellow", bold=True)
                    click.echo(f"  {status_data['branch']}")
                    click.echo()
                
                if "duration" in status_data:
                    click.secho("Duration:", fg="yellow", bold=True)
                    click.echo(f"  {status_data['duration']:.2f} seconds")
                    click.echo()
                
                if "public_url" in status_data:
                    click.secho("Results URL:", fg="yellow", bold=True)
                    click.secho(f"  {status_data['public_url']}", fg="green")
                    click.echo()
                
                if "presigned_url" in status_data:
                    click.secho("Presigned URL (expires in 1 hour):", fg="yellow", bold=True)
                    click.secho(f"  {status_data['presigned_url']}", fg="blue")
                    click.echo()
                
                # Display vulnerability summary if available
                if "summary" in status_data:
                    summary = status_data["summary"]
                    click.secho("Vulnerability Summary:", fg="yellow", bold=True)
                    click.echo(f"  Total Vulnerabilities: {summary.get('total_vulnerabilities', 0)}")
                    click.echo(f"  Total Targets Scanned: {summary.get('total_targets', 0)}")
                    click.echo()
                    
                    severity_breakdown = summary.get('severity_breakdown', {})
                    if severity_breakdown:
                        click.secho("  Severity Breakdown:", fg="yellow")
                        if severity_breakdown.get('CRITICAL', 0) > 0:
                            click.secho(f"    CRITICAL: {severity_breakdown['CRITICAL']}", fg="red", bold=True)
                        if severity_breakdown.get('HIGH', 0) > 0:
                            click.secho(f"    HIGH: {severity_breakdown['HIGH']}", fg="red")
                        if severity_breakdown.get('MEDIUM', 0) > 0:
                            click.secho(f"    MEDIUM: {severity_breakdown['MEDIUM']}", fg="yellow")
                        if severity_breakdown.get('LOW', 0) > 0:
                            click.secho(f"    LOW: {severity_breakdown['LOW']}", fg="blue")
                        if severity_breakdown.get('UNKNOWN', 0) > 0:
                            click.secho(f"    UNKNOWN: {severity_breakdown['UNKNOWN']}", fg="white")
                        click.echo()
                
                if "scan_date" in status_data:
                    click.secho("Scan Date:", fg="yellow", bold=True)
                    click.echo(f"  {status_data['scan_date']}")
                    click.echo()
                    
            elif status == "running":
                print_info(f"Status: {status}")
                if "message" in status_data:
                    click.echo(f"  {status_data['message']}")
                click.echo()
                click.echo("The scan is still in progress. Check back in a few moments.")
                click.echo()
                
            elif status == "failed":
                print_error(f"Status: {status}")
                if "error" in status_data:
                    click.echo(f"  Error: {status_data['error']}")
                click.echo()
            else:
                click.secho(f"Status: {status}", fg="yellow")
                click.echo()
            
            click.echo("=" * 60 + "\n")
            
    except PrismorAPIError as e:
        print_error(str(e))
        sys.exit(1)
    except Exception as e:
        print_error(f"Unexpected error: {str(e)}")
        sys.exit(1)


@cli.command()
def status():
    """Check your account status and GitHub integration."""
    try:
        client = PrismorClient()
        spinner = Spinner("Checking account status") if should_show_spinner() else None
        if spinner:
            spinner.start()
        try:
            auth_data = client.authenticate()
            if spinner:
                spinner.stop()
        except Exception as e:
            if spinner:
                spinner.stop()
            raise e
        
        click.echo("\n" + "=" * 60)
        click.secho("  Account Status", fg="cyan", bold=True)
        click.echo("=" * 60 + "\n")
        
        user_info = auth_data.get("user", {})
        if user_info:
                        
            repositories = user_info.get("repositories", [])
            click.secho(f"Connected Repositories: {len(repositories)}", fg="green")
            
            if repositories:
                click.echo("\nRepository List:")
                for repo in repositories:
                    click.echo(f"  • {repo.get('name', 'Unknown')} ({repo.get('htmlUrl', 'No URL')})")
            else:
                print_warning("No repositories connected.")
                click.echo("\nTo connect repositories:")
                click.echo("  1. Visit https://prismor.dev/dashboard")
                click.echo("  2. Connect your GitHub account")
                click.echo("  3. Select repositories to scan")
        else:
            print_error("Failed to retrieve account information.")
        
        click.echo("\n" + "=" * 60 + "\n")
        
    except PrismorAPIError as e:
        print_error(str(e))
        sys.exit(1)
    except Exception as e:
        print_error(f"Unexpected error: {str(e)}")
        sys.exit(1)


def main():
    """Entry point for the CLI."""
    cli()


if __name__ == "__main__":
    main()
