import argparse
import asyncio
import logging
import os
import sys
from typing import NoReturn

from auditor import __version__
from auditor.core.engine import AsyncKeyAuditor
from auditor.core.factory import ValidatorFactory


def _enable_windows_ansi() -> None:
    """Enable ANSI escape codes on Windows."""
    if sys.platform == "win32":
        try:
            import ctypes
            kernel32 = ctypes.windll.kernel32
            kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
        except Exception:
            pass


_enable_windows_ansi()


# ANSI color codes for terminal output
class Colors:
    GREEN = "\033[92m"
    YELLOW = "\033[93m"
    BLUE = "\033[94m"
    GRAY = "\033[90m"
    RED = "\033[91m"
    RESET = "\033[0m"
    BOLD = "\033[1m"


def setup_logging(verbose: bool = False) -> None:
    """Configure logging based on verbosity level."""
    if verbose:
        logging.basicConfig(
            level=logging.DEBUG,
            format="%(asctime)s [%(levelname)s] %(message)s",
            datefmt="%H:%M:%S"
        )
    else:
        # Suppress all logging unless verbose
        logging.basicConfig(level=logging.CRITICAL)


def print_banner() -> None:
    """Display the application banner."""
    banner = f"""
{Colors.BLUE}{'='*60}
  LLMLeaks v{__version__}
  Security Research Tool for Credential Detection
{'='*60}{Colors.RESET}
"""
    print(banner)


def create_key_callback(quiet: bool):
    """Create a callback function for key discovery notifications."""
    def on_key_found(provider: str, key: str, is_valid: bool) -> None:
        if quiet:
            return
        
        if is_valid:
            print(
                f"{Colors.GREEN}[+] {provider.upper()}: VALID "
                f"({key[:12]}...){Colors.RESET}"
            )
        else:
            print(
                f"{Colors.GRAY}[-] {provider.upper()}: revoked/placeholder "
                f"({key[:12]}...){Colors.RESET}"
            )
    
    return on_key_found


def create_page_callback(quiet: bool, total_pages: int):
    """Create a callback for page processing notifications."""
    def on_page_start(page: int) -> None:
        if quiet:
            return
        print(f"{Colors.BLUE}[*] Processing page {page}/{total_pages}...{Colors.RESET}")
    
    return on_page_start


def build_parser() -> argparse.ArgumentParser:
    """Construct the argument parser."""
    parser = argparse.ArgumentParser(
        prog="llmleaks",
        description=(
            "Scan GitHub repositories for exposed LLM/AI API keys "
            "and validate their status."
        ),
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s --token ghp_xxx --query "OPENAI_API_KEY"
  %(prog)s --token ghp_xxx --query "sk-proj-" --pages 10
  %(prog)s --token ghp_xxx --query "API_KEY" --out results.txt

Environment Variables:
  GITHUB_TOKEN    GitHub Personal Access Token (alternative to --token)

Supported Providers:
  OpenAI, Anthropic, Google Gemini, DeepSeek, Groq, Perplexity, Hugging Face
"""
    )
    
    # Required arguments
    parser.add_argument(
        "--token",
        default=os.getenv("GITHUB_TOKEN"),
        help="GitHub Personal Access Token (or set GITHUB_TOKEN env var)"
    )
    
    # Scan configuration
    parser.add_argument(
        "--query", "-q",
        default="API_KEY",
        help="GitHub code search query (default: API_KEY)"
    )
    parser.add_argument(
        "--pages", "-p",
        type=int,
        default=5,
        help="Number of result pages to scan (default: 5)"
    )
    parser.add_argument(
        "--out", "-o",
        default="valid_keys.txt",
        help="Output file for valid keys (default: valid_keys.txt)"
    )
    
    # Performance options
    parser.add_argument(
        "--concurrency", "-c",
        type=int,
        default=5,
        help="Maximum concurrent requests (default: 5)"
    )
    
    # Output options
    parser.add_argument(
        "--quiet",
        action="store_true",
        help="Suppress progress output"
    )
    parser.add_argument(
        "--verbose", "-v",
        action="store_true",
        help="Enable debug logging"
    )
    
    # Information
    parser.add_argument(
        "--version",
        action="version",
        version=f"%(prog)s {__version__}"
    )
    parser.add_argument(
        "--list-providers",
        action="store_true",
        help="List all supported API providers"
    )
    
    return parser


def list_providers() -> None:
    """Display all supported API providers."""
    print(f"\n{Colors.BOLD}Supported API Providers:{Colors.RESET}\n")
    
    for name, config in ValidatorFactory.get_all().items():
        print(f"  - {name.capitalize()}")
        print(f"    Pattern: {config.pattern.pattern[:50]}...")
    
    print()


async def run_audit(args: argparse.Namespace) -> int:
    """Execute the audit with provided arguments."""
    if not args.token:
        print(
            f"{Colors.RED}Error: GitHub token required. "
            f"Use --token or set GITHUB_TOKEN env var.{Colors.RESET}",
            file=sys.stderr
        )
        return 1
    
    if not args.quiet:
        print_banner()
        print(f"Query: {args.query}")
        print(f"Pages: {args.pages}")
        print(f"Output: {args.out}")
        print()
    
    auditor = AsyncKeyAuditor(
        github_token=args.token,
        max_concurrent=args.concurrency,
        on_key_found=create_key_callback(args.quiet),
        on_page_start=create_page_callback(args.quiet, args.pages)
    )
    
    try:
        stats = await auditor.run(
            query=args.query,
            pages=args.pages,
            output_file=args.out
        )
        
        if not args.quiet:
            print(f"\n{Colors.BLUE}{'='*60}{Colors.RESET}")
            print(f"Scan Complete")
            print(f"  Files scanned: {stats['scanned']}")
            print(f"  Unique keys found: {stats['unique_keys']}")
            print(f"  Valid keys: {stats['valid']}")
            
            if stats['valid'] > 0:
                print(f"\nResults saved to: {args.out}")
        
        return 0
        
    except KeyboardInterrupt:
        print(f"\n{Colors.YELLOW}Scan interrupted by user.{Colors.RESET}")
        return 130


def main() -> NoReturn:
    """Main entry point for the CLI."""
    parser = build_parser()
    args = parser.parse_args()
    
    if args.list_providers:
        list_providers()
        sys.exit(0)
    
    setup_logging(args.verbose)
    
    try:
        exit_code = asyncio.run(run_audit(args))
    except Exception as e:
        logging.error(f"Unexpected error: {e}")
        exit_code = 1
    
    sys.exit(exit_code)


if __name__ == "__main__":
    main()
