"""Core compliance checking logic."""
import json
import re
import requests
import pandas as pd
from typing import Optional, List, Dict, Any
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass, field
from .patterns import COMPILED_PATTERNS

PURPOSE_KEYWORDS = ["purpose", "objective", "description", "overview", "about"]
INPUT_KEYWORDS = ["input", "parameter", "argument", "config"]
OUTPUT_KEYWORDS = ["output", "result", "produces", "creates", "table", "dataset"]
CHANGE_HISTORY_KEYWORDS = ["change history", "version history", "changelog"]


@dataclass
class ComplianceResults:
    """Container for compliance results."""
    results: List[Dict[str, Any]] = field(default_factory=list)
    workspace_id: str = ""
    analysis_mode: str = ""

    @property
    def total(self) -> int:
        return len(self.results)

    @property
    def passing(self) -> int:
        return len([r for r in self.results if r.get("compliance_score", 0) == 100])

    @property
    def failing(self) -> int:
        return self.total - self.passing

    @property
    def avg_score(self) -> float:
        if not self.results:
            return 0.0
        return sum(r.get("compliance_score", 0) for r in self.results) / len(self.results)

    def summary(self) -> None:
        """Print compliance summary."""
        print(f"\n{'='*60}")
        print("📊 COMPLIANCE SUMMARY")
        print(f"{'='*60}")
        print(f"   Total Notebooks: {self.total}")
        print(f"   ✅ Passing:      {self.passing}")
        print(f"   ❌ Failing:      {self.failing}")
        print(f"   📈 Avg Score:    {self.avg_score:.1f}%")
        print(f"{'='*60}")
        if self.failing > 0:
            print("\n📋 Failing Notebooks:")
            for r in sorted(self.results, key=lambda x: x.get("compliance_score", 0)):
                if r.get("compliance_score", 0) < 100:
                    print(f"   ❌ {r['notebook_name']}: {r['compliance_score']:.0f}%")
                    if r.get("issues") and r["issues"] != "Compliant":
                        for issue in r["issues"].split("; ")[:3]:
                            print(f"      • {issue}")

    def to_dataframe(self) -> pd.DataFrame:
        """Convert to pandas DataFrame."""
        return pd.DataFrame(self.results)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary."""
        return {
            "workspace_id": self.workspace_id,
            "analysis_mode": self.analysis_mode,
            "summary": {
                "total": self.total,
                "passing": self.passing,
                "failing": self.failing,
                "avg_score": round(self.avg_score, 1),
            },
            "notebooks": self.results,
        }

    def to_html(self) -> str:
        """Generate HTML report."""
        pass_rate = self.passing / self.total if self.total > 0 else 0
        color = "#28a745" if pass_rate >= 0.9 else "#ffc107" if pass_rate >= 0.7 else "#dc3545"
        rows = ""
        for r in sorted(self.results, key=lambda x: x.get("compliance_score", 0)):
            score = r.get("compliance_score", 0)
            status = "✅" if score == 100 else "❌"
            row_color = "#d4edda" if score == 100 else "#f8d7da" if score < 70 else "#fff3cd"
            rows += f'<tr style="background-color:{row_color}"><td>{status}</td><td>{r.get("notebook_name", "")}</td><td><b>{score:.0f}%</b></td><td>{r.get("issues", "")[:80]}</td></tr>'
        return f"""<!DOCTYPE html><html><head><style>body{{font-family:'Segoe UI',sans-serif;margin:20px}}.header{{background:{color};color:white;padding:20px;border-radius:8px}}table{{width:100%;border-collapse:collapse;margin-top:20px}}th,td{{padding:12px;text-align:left;border-bottom:1px solid #ddd}}th{{background:#343a40;color:white}}</style></head><body><div class="header"><h1>📋 Compliance Report</h1></div><p>Total: {self.total} | Pass: {self.passing} | Fail: {self.failing} | Avg: {self.avg_score:.1f}%</p><table><tr><th>Status</th><th>Notebook</th><th>Score</th><th>Issues</th></tr>{rows}</table></body></html>"""

    def post_to_teams(self, webhook_url: str) -> bool:
        """Post results to Teams webhook."""
        color = "00FF00" if self.failing == 0 else "FFA500" if self.avg_score >= 70 else "FF0000"
        card = {
            "@type": "MessageCard",
            "@context": "http://schema.org/extensions",
            "themeColor": color,
            "summary": f"Compliance: {self.avg_score:.1f}%",
            "sections": [
                {
                    "activityTitle": "📋 Notebook Compliance Results",
                    "facts": [
                        {"name": "Total", "value": str(self.total)},
                        {"name": "Passing", "value": f"✅ {self.passing}"},
                        {"name": "Failing", "value": f"❌ {self.failing}"},
                        {"name": "Avg Score", "value": f"{self.avg_score:.1f}%"},
                    ],
                }
            ],
        }
        try:
            r = requests.post(webhook_url, json=card, timeout=30)
            success = r.status_code == 200
            print("✅ Posted to Teams" if success else f"❌ Teams failed: {r.status_code}")
            return success
        except Exception as e:
            print(f"❌ Teams error: {e}")
            return False


def _get_auth_token():
    """Get Fabric API token."""
    from notebookutils import mssparkutils
    return mssparkutils.credentials.getToken("pbi")


def check_hardcoded_values(code: str) -> Dict[str, Any]:
    """Check for hardcoded values."""
    findings = []
    for pattern, desc in COMPILED_PATTERNS:
        for match in pattern.findall(code):
            findings.append(f"{desc}: {str(match)[:40]}...")
    return {"has_hardcoding": len(findings) > 0, "count": len(findings), "findings": findings[:10]}


def check_notebook_standards(nb_json: Dict) -> Dict[str, Any]:
    """Check notebook against standards."""
    cells = nb_json.get("cells", [])
    issues = []

    def get_src(c):
        s = c.get("source", [])
        return "\n".join(s) if isinstance(s, list) else s

    # Check 1: Markdown first
    has_markdown_first = cells and cells[0].get("cell_type") == "markdown"
    if not has_markdown_first:
        issues.append("First cell should be markdown")

    # Get all markdown
    all_md = " ".join(get_src(c) for c in cells if c.get("cell_type") == "markdown").lower()

    # Check 2-5: Documentation
    has_purpose = any(k in all_md for k in PURPOSE_KEYWORDS)
    has_input_params = any(k in all_md for k in INPUT_KEYWORDS)
    has_output_tables = any(k in all_md for k in OUTPUT_KEYWORDS)
    has_change_history = any(k in all_md for k in CHANGE_HISTORY_KEYWORDS)

    if not has_purpose:
        issues.append("Missing purpose section")
    if not has_input_params:
        issues.append("Missing input documentation")
    if not has_output_tables:
        issues.append("Missing output documentation")
    if not has_change_history:
        issues.append("Missing change history")

    # Get all code
    code_cells = [c for c in cells if c.get("cell_type") == "code"]
    all_code = "\n".join(get_src(c) for c in code_cells)

    # Check 6: Imports grouped
    has_imports_grouped = True
    if code_cells:
        first = get_src(code_cells[0])
        rest = "\n".join(get_src(c) for c in code_cells[1:])
        first_imports = len(re.findall(r"^(?:import|from)\s+\w+", first, re.M))
        rest_imports = len(re.findall(r"^(?:import|from)\s+\w+", rest, re.M))
        if rest_imports > first_imports:
            has_imports_grouped = False
            issues.append("Group imports at start")

    # Check 7: Snake case
    funcs = re.findall(r"def\s+([a-zA-Z_]\w*)\s*\(", all_code)
    violations = [f for f in funcs if not re.match(r"^[a-z_][a-z0-9_]*$", f) and not f.isupper()]
    uses_snake_case = len(violations) == 0
    if not uses_snake_case:
        issues.append(f"Use snake_case: {', '.join(violations[:3])}")

    # Check 8: Expensive ops
    expensive = []
    for pat, name in [(r"\.collect\s*\(", "collect"), (r"\.toPandas\s*\(", "toPandas")]:
        cnt = len(re.findall(pat, all_code))
        if cnt > 0:
            expensive.append(f"{name}:{cnt}")
    has_minimal_expensive = len(expensive) <= 3
    if not has_minimal_expensive:
        issues.append(f"Too many expensive ops: {', '.join(expensive)}")

    # Check 9: Hardcoding
    hc = check_hardcoded_values(all_code)
    has_no_hardcoding = not hc["has_hardcoding"]
    if not has_no_hardcoding:
        issues.append(f"Hardcoded values: {hc['count']}")

    return {
        "has_markdown_first": has_markdown_first,
        "has_purpose": has_purpose,
        "has_input_params": has_input_params,
        "has_output_tables": has_output_tables,
        "has_change_history": has_change_history,
        "has_imports_grouped": has_imports_grouped,
        "uses_snake_case": uses_snake_case,
        "has_minimal_expensive_ops": has_minimal_expensive,
        "has_no_hardcoding": has_no_hardcoding,
        "issues": issues,
        "total_cells": len(cells),
    }


def calculate_score(check: Dict) -> float:
    """Calculate compliance score."""
    checks = [
        "has_markdown_first", "has_purpose", "has_input_params", "has_output_tables",
        "has_change_history", "has_imports_grouped", "uses_snake_case",
        "has_minimal_expensive_ops", "has_no_hardcoding",
    ]
    return (sum(1 for c in checks if check.get(c, False)) / len(checks)) * 100


def check_compliance(
    workspace_id: str,
    notebook_name: Optional[str] = None,
    folder_filter: Optional[List[str]] = None,
    mode: str = "all_notebooks",
    max_workers: int = 10,
    show_progress: bool = True,
) -> ComplianceResults:
    """
    Check notebook compliance against best practices.

    Args:
        workspace_id: Fabric workspace GUID
        notebook_name: Single notebook name (auto-sets mode)
        folder_filter: Folder names to filter (auto-sets mode)
        mode: "all_notebooks", "folder", or "single_notebook"
        max_workers: Parallel threads (default: 10)
        show_progress: Print progress (default: True)

    Returns:
        ComplianceResults with summary(), to_dataframe(), post_to_teams()

    Example:
        results = check_compliance(workspace_id="abc-123...")
        results.summary()
    """
    from notebookutils import notebookutils

    if notebook_name:
        mode = "single_notebook"
    if folder_filter:
        mode = "folder"

    if show_progress:
        print(f"🔍 Workspace: {workspace_id[:8]}... | Mode: {mode}")

    headers = {"Authorization": f"Bearer {_get_auth_token()}"}

    # Get notebooks
    r = requests.get(
        f"https://api.fabric.microsoft.com/v1/workspaces/{workspace_id}/items?type=Notebook",
        headers=headers,
        timeout=30,
    )
    if r.status_code == 403:
        raise PermissionError(f"Access denied to workspace {workspace_id}")
    if r.status_code != 200:
        raise RuntimeError(f"API error: {r.status_code}")

    notebooks = r.json().get("value", [])

    # Filter
    if mode == "single_notebook":
        notebooks = [n for n in notebooks if n["displayName"] == notebook_name]
        if not notebooks:
            raise ValueError(f"Notebook '{notebook_name}' not found")
    elif mode == "folder" and folder_filter:
        fr = requests.get(
            f"https://api.fabric.microsoft.com/v1/workspaces/{workspace_id}/folders",
            headers=headers,
            timeout=30,
        )
        folders = fr.json().get("value", []) if fr.status_code == 200 else []
        fids = [f["id"] for f in folders if any(ff.lower() in f.get("displayName", "").lower() for ff in folder_filter)]
        notebooks = [n for n in notebooks if n.get("parentFolderId") in fids]

    if show_progress:
        print(f"   Found {len(notebooks)} notebook(s)")

    if not notebooks:
        return ComplianceResults(workspace_id=workspace_id, analysis_mode=mode)

    def analyze(nb):
        name = nb["displayName"]
        try:
            content = notebookutils.notebook.getDefinition(name, workspaceId=workspace_id)
            check = check_notebook_standards(json.loads(content))
            return {
                "notebook_name": name,
                "folder_path": nb.get("parentFolderId", "root"),
                "compliance_score": calculate_score(check),
                "issues": "; ".join(check["issues"]) if check["issues"] else "Compliant",
                **{k: v for k, v in check.items() if k != "issues"},
            }
        except Exception as e:
            return {"notebook_name": name, "folder_path": nb.get("parentFolderId", "root"), "compliance_score": 0, "issues": f"Error: {e}"}

    results = []
    with ThreadPoolExecutor(max_workers=max_workers) as ex:
        futures = {ex.submit(analyze, nb): nb["displayName"] for nb in notebooks}
        for i, f in enumerate(as_completed(futures), 1):
            res = f.result()
            results.append(res)
            if show_progress:
                status = "✅" if res.get("compliance_score", 0) == 100 else "❌"
                print(f"   [{i}/{len(notebooks)}] {status} {res['notebook_name']}")

    return ComplianceResults(results=results, workspace_id=workspace_id, analysis_mode=mode)
