"""
spidercrypt.audit
~~~~~~~~~~~~~~~~~
Static secret scanning for source files.
Supports: text files, .xlsx, .pdf, .docx, .accdb/.mdb
          + directory scanning (recursive or flat)
"""

import re
from pathlib import Path
from typing import List, Dict, Any, Optional, Set

from .patterns import SECRET_PATTERNS, RISKY_CODE_PATTERNS

# Extensions supportées pour le scan de dossier
SUPPORTED_EXTENSIONS: Set[str] = {
    ".py", ".js", ".ts", ".jsx", ".tsx",
    ".env", ".txt", ".yml", ".yaml",
    ".json", ".toml", ".ini", ".cfg", ".conf",
    ".sh", ".bash", ".zsh",
    ".xml", ".html", ".htm",
    ".xlsx", ".xls",
    ".pdf",
    ".docx", ".doc",
    ".accdb", ".mdb",
}

# ────────────────────────────────────────────────
# FILE PARSERS
# ────────────────────────────────────────────────

def _read_xlsx(filepath: str) -> str:
    try:
        import openpyxl
        wb = openpyxl.load_workbook(filepath, data_only=True)
        lines = []
        for sheet in wb.worksheets:
            for row in sheet.iter_rows(values_only=True):
                lines.append(" ".join(str(c) for c in row if c is not None))
        return "\n".join(lines)
    except ImportError:
        raise RuntimeError("openpyxl not installed. Run: pip install openpyxl")


def _read_pdf(filepath: str) -> str:
    try:
        import pdfplumber
        with pdfplumber.open(filepath) as pdf:
            return "\n".join(page.extract_text() or "" for page in pdf.pages)
    except ImportError:
        raise RuntimeError("pdfplumber not installed. Run: pip install pdfplumber")


def _read_docx(filepath: str) -> str:
    try:
        from docx import Document
        doc = Document(filepath)
        return "\n".join(p.text for p in doc.paragraphs)
    except ImportError:
        raise RuntimeError("python-docx not installed. Run: pip install python-docx")


def _read_accdb(filepath: str) -> str:
    try:
        import pyodbc
        conn_str = (
            r"DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};"
            rf"DBQ={filepath};"
        )
        conn   = pyodbc.connect(conn_str)
        cursor = conn.cursor()
        lines  = []

        for table in cursor.tables(tableType="TABLE"):
            table_name = table.table_name
            lines.append(f"[TABLE: {table_name}]")
            try:
                cursor.execute(f"SELECT * FROM [{table_name}]")
                for row in cursor.fetchall():
                    lines.append(" ".join(str(c) for c in row if c is not None))
            except Exception:
                pass

        conn.close()
        return "\n".join(lines)
    except ImportError:
        raise RuntimeError("pyodbc not installed. Run: pip install pyodbc")


def _read_file(filepath: str) -> tuple[str, str]:
    path = Path(filepath)

    # ── Guard: dossier ──
    if path.is_dir():
        raise IsADirectoryError(
            f"'{filepath}' is a directory. Use audit_directory() to scan folders."
        )

    # ── Guard: permission / existence ──
    if not path.exists():
        raise FileNotFoundError(f"File not found: {filepath}")

    ext = path.suffix.lower()

    if ext in (".xlsx", ".xls"):
        return _read_xlsx(filepath), "excel"
    elif ext == ".pdf":
        return _read_pdf(filepath), "pdf"
    elif ext in (".docx", ".doc"):
        return _read_docx(filepath), "word"
    elif ext in (".accdb", ".mdb"):
        return _read_accdb(filepath), "access"
    else:
        text = path.read_text(encoding="utf-8", errors="replace")
        return text, "text"


# ────────────────────────────────────────────────
# SCANNERS
# ────────────────────────────────────────────────

def scan_secrets(text: str) -> List[str]:
    return [p for p in SECRET_PATTERNS if re.search(p, text)]


def scan_risky_code(text: str) -> List[str]:
    return [p for p in RISKY_CODE_PATTERNS if p in text]


# ────────────────────────────────────────────────
# MAIN AUDIT — SINGLE FILE
# ────────────────────────────────────────────────

def audit_file(filepath: str, ai: bool = False) -> Dict[str, Any]:
    try:
        code, file_type = _read_file(filepath)
    except (RuntimeError, IsADirectoryError, PermissionError, FileNotFoundError) as e:
        return {
            "file":            filepath,
            "error":           str(e),
            "secrets_found":   0,
            "secret_patterns": [],
            "risky_code":      [],
        }

    secrets = scan_secrets(code)
    risky   = scan_risky_code(code)

    report: Dict[str, Any] = {
        "file":            filepath,
        "file_type":       file_type,
        "secrets_found":   len(secrets),
        "secret_patterns": secrets,
        "risky_code":      risky,
    }

    if ai:
        try:
            from langchain_ollama import OllamaLLM
            from langchain_core.prompts import ChatPromptTemplate
            from langchain_core.output_parsers import StrOutputParser

            prompt = ChatPromptTemplate.from_messages([
                ("system", "You are a security expert. Analyse the following code for vulnerabilities."),
                ("human", "{code}"),
            ])
            chain = prompt | OllamaLLM(model="llama3") | StrOutputParser()
            report["ai_analysis"] = chain.invoke({"code": code[:4000]})
        except ImportError:
            report["ai_analysis"] = "langchain-ollama not installed."

    return report


# ────────────────────────────────────────────────
# MAIN AUDIT — DIRECTORY
# ────────────────────────────────────────────────

def audit_directory(
    dirpath: str,
    ai: bool = False,
    recursive: bool = True,
    extensions: Optional[Set[str]] = None,
) -> Dict[str, Any]:
    """
    Scan tous les fichiers supportés dans un dossier.

    Args:
        dirpath:    Chemin du dossier à scanner.
        ai:         Active l'analyse IA sur chaque fichier.
        recursive:  Si True, descend dans les sous-dossiers.
        extensions: Set d'extensions à inclure (défaut: SUPPORTED_EXTENSIONS).

    Returns:
        {
            "directory":     str,
            "files_scanned": int,
            "total_secrets": int,
            "files_with_secrets": int,
            "files_with_errors":  int,
            "reports":       List[Dict],
        }
    """
    root = Path(dirpath)
    exts = extensions or SUPPORTED_EXTENSIONS

    if not root.is_dir():
        raise NotADirectoryError(f"'{dirpath}' is not a valid directory.")

    # Collect files
    if recursive:
        files = sorted(f for f in root.rglob("*") if f.is_file() and f.suffix.lower() in exts)
    else:
        files = sorted(f for f in root.iterdir() if f.is_file() and f.suffix.lower() in exts)

    reports        = []
    total_secrets  = 0
    files_w_secrets = 0
    files_w_errors  = 0

    for f in files:
        report = audit_file(str(f), ai=ai)
        reports.append(report)

        if "error" in report:
            files_w_errors += 1
        else:
            sec = report.get("secrets_found", 0)
            total_secrets += sec
            if sec > 0:
                files_w_secrets += 1

    return {
        "directory":          dirpath,
        "files_scanned":      len(files),
        "total_secrets":      total_secrets,
        "files_with_secrets": files_w_secrets,
        "files_with_errors":  files_w_errors,
        "reports":            reports,
    }