"""
utils/models.py — VaultHound v1.0.0
All dataclasses and enums.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Optional


# ── Enums ─────────────────────────────────────────────────────────────────────

class ScanMode(str, Enum):
    URL = "url"
    DIR = "dir"


class Severity(str, Enum):
    CRITICAL = "critical"
    HIGH     = "high"
    MEDIUM   = "medium"
    LOW      = "low"
    INFO     = "info"

    @classmethod
    def rank(cls, sev: str) -> int:
        order = {
            "critical": 5, "high": 4, "medium": 3, "low": 2, "info": 1,
        }
        return order.get(sev.lower(), 0)


# ── Config ────────────────────────────────────────────────────────────────────

@dataclass
class ScanConfig:
    target:        str
    mode:          ScanMode          = ScanMode.URL
    scan_git:      bool              = False
    scan_js:       bool              = True
    crawl_depth:   int               = 3
    threads:       int               = 10
    timeout:       int               = 10
    min_severity:  str               = "low"
    patterns_file: Optional[str]     = None
    output_dir:    str               = "vaulthound_reports"
    no_report:     bool              = False

    def to_dict(self) -> dict:
        return {
            "target":        self.target,
            "mode":          self.mode.value,
            "scan_git":      self.scan_git,
            "scan_js":       self.scan_js,
            "crawl_depth":   self.crawl_depth,
            "threads":       self.threads,
            "timeout":       self.timeout,
            "min_severity":  self.min_severity,
            "output_dir":    self.output_dir,
        }


# ── Finding ───────────────────────────────────────────────────────────────────

@dataclass
class SecretFinding:
    """A single discovered secret or credential."""
    pattern_name:  str               # e.g. "AWS Access Key"
    severity:      str               # critical / high / medium / low / info
    match:         str               # the actual matched string (may be redacted)
    source:        str               # file path or URL where found
    line_number:   int               = 0
    line_content:  str               = ""   # surrounding context (redacted)
    context:       str               = ""   # extra context (git commit hash, etc.)
    redacted:      bool              = True # always True — never store raw secrets

    @property
    def display_match(self) -> str:
        """Show first 6 chars + asterisks for security."""
        if len(self.match) <= 8:
            return self.match[:2] + "****"
        return self.match[:6] + "****" + self.match[-2:]

    def to_dict(self) -> dict:
        return {
            "pattern_name": self.pattern_name,
            "severity":     self.severity,
            "match":        self.display_match,
            "source":       self.source,
            "line_number":  self.line_number,
            "context":      self.context,
        }


# ── Scan result ───────────────────────────────────────────────────────────────

@dataclass
class ScanResult:
    target:       str
    mode:         str
    start_time:   str
    end_time:     str               = ""
    findings:     list[SecretFinding] = field(default_factory=list)
    files_scanned: list[str]        = field(default_factory=list)
    urls_scanned:  list[str]        = field(default_factory=list)
    git_commits_scanned: int        = 0
    errors:        list[str]        = field(default_factory=list)

    # Stats
    @property
    def total(self) -> int:
        return len(self.findings)

    @property
    def critical_count(self) -> int:
        return sum(1 for f in self.findings if f.severity == "critical")

    @property
    def high_count(self) -> int:
        return sum(1 for f in self.findings if f.severity == "high")

    @property
    def medium_count(self) -> int:
        return sum(1 for f in self.findings if f.severity == "medium")

    @property
    def low_count(self) -> int:
        return sum(1 for f in self.findings if f.severity == "low")

    @property
    def by_pattern(self) -> dict[str, int]:
        counts: dict[str, int] = {}
        for f in self.findings:
            counts[f.pattern_name] = counts.get(f.pattern_name, 0) + 1
        return dict(sorted(counts.items(), key=lambda x: x[1], reverse=True))

    @property
    def by_source(self) -> dict[str, list[SecretFinding]]:
        sources: dict[str, list[SecretFinding]] = {}
        for f in self.findings:
            sources.setdefault(f.source, []).append(f)
        return sources

    def filter_by_severity(self, min_sev: str) -> list[SecretFinding]:
        min_rank = Severity.rank(min_sev)
        return [f for f in self.findings if Severity.rank(f.severity) >= min_rank]

    def to_dict(self) -> dict:
        return {
            "target":               self.target,
            "mode":                 self.mode,
            "start_time":           self.start_time,
            "end_time":             self.end_time,
            "total_findings":       self.total,
            "critical":             self.critical_count,
            "high":                 self.high_count,
            "medium":               self.medium_count,
            "low":                  self.low_count,
            "files_scanned":        len(self.files_scanned),
            "urls_scanned":         len(self.urls_scanned),
            "git_commits_scanned":  self.git_commits_scanned,
            "findings":             [f.to_dict() for f in self.findings],
            "errors":               self.errors,
        }
