from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
import json
import logging
import os
import sys
from pathlib import Path
from typing import Optional

from app.database import SessionDep

router = APIRouter(prefix="/api/settings", tags=["settings"])

# In frozen PyInstaller builds, write to %APPDATA%\CerebionRivet\ (writable).
# In development, write to data/ next to the app package.
if getattr(sys, "frozen", False):
    _appdata = os.environ.get("APPDATA") or str(Path.home())
    _data_dir = Path(_appdata) / "CerebionRivet"
else:
    _data_dir = Path(__file__).parent.parent.parent / "data"  # rivet_server/data/
_data_dir.mkdir(parents=True, exist_ok=True)
SETTINGS_FILE = _data_dir / "settings.json"

class BinaryAnalyzerSettings(BaseModel):
    max_binary_size_mb: int = Field(default=500, ge=1, le=5000, description="Maximum binary file size in MB")
    analysis_timeout_seconds: int = Field(default=600, ge=30, le=3600, description="Analysis timeout in seconds")
    max_memory_mb: int = Field(default=2048, ge=512, le=8192, description="Maximum memory usage in MB")
    max_concurrent_scans: int = Field(default=3, ge=1, le=10, description="Maximum concurrent scans")
    max_archive_size_mb: int = Field(default=500, ge=1, le=2000, description="Maximum archive size in MB")
    max_files_per_archive: int = Field(default=100, ge=1, le=1000, description="Maximum files to extract per archive")
    analysis_backend: str = Field(default="lief", description="Analysis backend: 'lief' or 'ghidra'")

DEFAULT_NETWORK_PORTS = [443, 465, 587, 636, 993, 995, 5671, 8443]

class NetworkAnalyzerSettings(BaseModel):
    ports: list[int] = Field(default_factory=lambda: DEFAULT_NETWORK_PORTS, description="Ports to scan")
    max_bulk_targets: int = Field(default=50, ge=1, le=500, description="Maximum targets in a bulk scan")

class LicenseSettings(BaseModel):
    license_server_url: str = Field(default="https://license.cerebion.com", description="License server URL")
    offline_public_key: str = Field(default="", description="Ed25519 public key for offline license verification")

class AppSettings(BaseModel):
    binary_analyzer: BinaryAnalyzerSettings = Field(default_factory=BinaryAnalyzerSettings)
    network_analyzer: NetworkAnalyzerSettings = Field(default_factory=NetworkAnalyzerSettings)
    license: LicenseSettings = Field(default_factory=LicenseSettings)
    last_folder_path: Optional[str] = None

def load_settings() -> AppSettings:
    """Load settings from file or return defaults"""
    if SETTINGS_FILE.exists():
        try:
            data = json.loads(SETTINGS_FILE.read_text())
            return AppSettings(**data)
        except Exception as e:
            print(f"Error loading settings: {e}")
    return AppSettings()

def save_settings(settings: AppSettings):
    """Save settings to file"""
    SETTINGS_FILE.parent.mkdir(parents=True, exist_ok=True)
    SETTINGS_FILE.write_text(json.dumps(settings.dict(), indent=2))

@router.get("/binary-analyzer")
def get_binary_analyzer_settings():
    """Get binary analyzer settings with Ghidra availability"""
    settings = load_settings()
    
    # Check if Ghidra is available
    ghidra_available = False
    try:
        from common.ghidra import is_available as ghidra_is_available
        ghidra_available = ghidra_is_available()
    except:
        pass
    
    result = settings.binary_analyzer.dict()
    result["ghidra_available"] = ghidra_available
    return result

@router.put("/binary-analyzer")
def update_binary_analyzer_settings(new_settings: BinaryAnalyzerSettings):
    """Update binary analyzer settings"""
    settings = load_settings()
    settings.binary_analyzer = new_settings
    save_settings(settings)
    return {"status": "success", "settings": new_settings.dict()}

@router.get("/network-analyzer")
def get_network_analyzer_settings():
    return load_settings().network_analyzer.dict()

@router.put("/network-analyzer")
def update_network_analyzer_settings(new_settings: NetworkAnalyzerSettings):
    settings = load_settings()
    settings.network_analyzer = new_settings
    save_settings(settings)
    return {"status": "success", "settings": new_settings.dict()}

@router.get("/license")
def get_license_settings():
    return load_settings().license.dict()

@router.put("/license")
def update_license_settings(new_settings: LicenseSettings):
    settings = load_settings()
    settings.license = new_settings
    save_settings(settings)
    return {"status": "success", "settings": new_settings.dict()}

@router.get("/workspace")
def get_workspace():
    return {"last_folder_path": load_settings().last_folder_path}

@router.put("/workspace")
def set_workspace(body: dict):
    settings = load_settings()
    settings.last_folder_path = body.get("last_folder_path")
    save_settings(settings)
    return {"last_folder_path": settings.last_folder_path}

@router.get("/all")
def get_all_settings():
    """Get all application settings"""
    return load_settings().dict()

@router.delete("/scan-cache")
def clear_scan_cache(project_root: Optional[str] = None, session: SessionDep = None):
    """Clear the incremental scan cache.

    Pass ?project_root=<path> to clear only a specific project's cache.
    Omit to clear all cached results.
    """
    from app.code_analyzer.scan_cache import ScanCache
    cache = ScanCache(session)
    if project_root:
        count = cache.clear_project(project_root)
        return {"success": True, "entries_cleared": count, "scope": project_root}
    count = cache.clear_all()
    return {"success": True, "entries_cleared": count, "scope": "all"}


@router.get("/scan-cache/stats")
def get_scan_cache_stats(project_root: Optional[str] = None, session: SessionDep = None):
    """Return cache statistics (entry count, languages, oldest/newest entry)."""
    from app.code_analyzer.scan_cache import ScanCache
    cache = ScanCache(session)
    return cache.get_stats(project_root)


# Global settings accessor
def get_binary_analyzer_limits():
    """Get current binary analyzer limits for use in analyzers"""
    settings = load_settings()
    return {
        "max_binary_size": settings.binary_analyzer.max_binary_size_mb * 1_000_000,
        "analysis_timeout": settings.binary_analyzer.analysis_timeout_seconds,
        "max_memory_mb": settings.binary_analyzer.max_memory_mb,
        "max_concurrent_scans": settings.binary_analyzer.max_concurrent_scans,
        "max_archive_size": settings.binary_analyzer.max_archive_size_mb * 1_000_000,
        "max_files_per_archive": settings.binary_analyzer.max_files_per_archive,
        "analysis_backend": settings.binary_analyzer.analysis_backend
    }

_VALID_LOG_LEVELS = {"DEBUG", "INFO", "WARNING", "ERROR"}

class LogLevelRequest(BaseModel):
    level: str = Field(description="Log level: DEBUG, INFO, WARNING, or ERROR")

@router.get("/log-level")
def get_log_level():
    """Get the current root log level."""
    return {"level": logging.getLevelName(logging.getLogger().level)}

@router.put("/log-level")
def set_log_level(body: LogLevelRequest):
    """Set the root log level at runtime (takes effect immediately, no restart needed)."""
    level = body.level.upper()
    if level not in _VALID_LOG_LEVELS:
        raise HTTPException(status_code=422, detail=f"Invalid log level. Must be one of: {', '.join(sorted(_VALID_LOG_LEVELS))}")
    logging.getLogger().setLevel(level)
    # Also update all file handlers so they filter at the new level
    for handler in logging.getLogger().handlers:
        if not isinstance(handler, logging.StreamHandler) or hasattr(handler, 'baseFilename'):
            handler.setLevel(level)
    logging.getLogger(__name__).info("Log level changed to %s", level)
    return {"level": level}
