"""
Run this script to create the fabric-notebook-compliance package.
Usage: python create_package.py
"""

import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# Create directories
os.makedirs(os.path.join(BASE_DIR, "src", "fabric_notebook_compliance"), exist_ok=True)

print("📦 Creating fabric-notebook-compliance package...")

# ==============================================================================
# pyproject.toml
# ==============================================================================
with open(os.path.join(BASE_DIR, "pyproject.toml"), "w", encoding="utf-8") as f:
    f.write('''[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "fabric-notebook-compliance"
version = "1.0.0"
description = "Best practices compliance checker for Microsoft Fabric notebooks"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.8"
dependencies = ["pandas>=1.0.0", "requests>=2.20.0"]
keywords = ["fabric", "microsoft", "notebook", "compliance", "best-practices"]
classifiers = [
    "Development Status :: 4 - Beta",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
]

[project.urls]
Homepage = "https://github.com/yourusername/fabric-notebook-compliance"
''')
print("   ✅ pyproject.toml")

# ==============================================================================
# README.md
# ==============================================================================
with open(os.path.join(BASE_DIR, "README.md"), "w", encoding="utf-8") as f:
    f.write('''# fabric-notebook-compliance

Best practices compliance checker for Microsoft Fabric notebooks.

## Installation

```bash
pip install fabric-notebook-compliance
```

## Quick Start

```python
from fabric_notebook_compliance import check_compliance

# Check all notebooks in a workspace
results = check_compliance(workspace_id="your-workspace-guid")
results.summary()

# Get pandas DataFrame
df = results.to_dataframe()

# Post to Teams
results.post_to_teams("https://your-webhook-url")
```

## Usage

```python
# Check single notebook
results = check_compliance(workspace_id="...", notebook_name="MyNotebook")

# Check specific folders
results = check_compliance(workspace_id="...", folder_filter=["FACT_ARR", "DIM_TSIOU"])
```

## Compliance Checks (9 total)

| Check | Description |
|-------|-------------|
| Markdown First | First cell should be markdown |
| Purpose | Document notebook purpose |
| Input Params | Document inputs |
| Output Tables | Document outputs |
| Change History | Version tracking |
| Imports Grouped | All imports at top |
| Snake Case | Use snake_case naming |
| Minimal Expensive Ops | Limit collect(), toPandas() |
| No Hardcoding | No hardcoded values |

## Requirements

- Must run inside Microsoft Fabric notebook
- Workspace access to notebooks being checked

## License

MIT
''')
print("   ✅ README.md")

# ==============================================================================
# LICENSE
# ==============================================================================
with open(os.path.join(BASE_DIR, "LICENSE"), "w", encoding="utf-8") as f:
    f.write('''MIT License

Copyright (c) 2026

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
''')
print("   ✅ LICENSE")

# ==============================================================================
# src/fabric_notebook_compliance/__init__.py
# ==============================================================================
with open(os.path.join(BASE_DIR, "src", "fabric_notebook_compliance", "__init__.py"), "w", encoding="utf-8") as f:
    f.write('''"""Fabric Notebook Compliance Checker."""
__version__ = "1.0.0"
from .checker import check_compliance, ComplianceResults
from .patterns import HARDCODING_PATTERNS
__all__ = ["check_compliance", "ComplianceResults", "HARDCODING_PATTERNS", "__version__"]
''')
print("   ✅ src/fabric_notebook_compliance/__init__.py")

# ==============================================================================
# src/fabric_notebook_compliance/patterns.py
# ==============================================================================
with open(os.path.join(BASE_DIR, "src", "fabric_notebook_compliance", "patterns.py"), "w", encoding="utf-8") as f:
    f.write('''"""Hardcoding detection patterns."""
import re

HARDCODING_PATTERNS = [
    (r'["\\'][0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}["\\']', "Hardcoded GUID"),
    (r'["\\']https?://(?!schema\\.org|api\\.fabric\\.microsoft\\.com)[^"\\']+(["\\'])', "Hardcoded URL"),
    (r'["\\'][A-Za-z]:\\\\[^"\\']+(["\\'])', "Hardcoded Windows path"),
    (r'["\\']abfss?://[^"\\']+(["\\'])', "Hardcoded ABFS path"),
    (r'(?:Password|Pwd)=[^;\\s]+', "Hardcoded password"),
    (r'(?:api[_-]?key|secret|token)\\s*=\\s*["\\'][^"\\']+["\\']', "Hardcoded credential"),
    (r'["\\'](?:19|20)\\d{2}[-/](?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12]\\d|3[01])["\\']', "Hardcoded date"),
    (r'["\\'](?:FY|CY)[-]?\\d{2,4}["\\']', "Hardcoded fiscal year"),
    (r'\\.filter\\s*\\([^)]*==\\s*["\\'][^"\\']+["\\']', "Hardcoded filter"),
    (r'\\.where\\s*\\([^)]*==\\s*["\\'][^"\\']+["\\']', "Hardcoded where"),
    (r'\\.isin\\s*\\(\\s*\\[[^\\]]*["\\'][^"\\']+["\\']', "Hardcoded isin"),
    (r'\\.when\\s*\\([^)]*==\\s*["\\'][^"\\']+["\\']', "Hardcoded when"),
    (r'lit\\s*\\(\\s*["\\'][^"\\']+["\\']\\s*\\)', "Hardcoded literal"),
    (r'\\.like\\s*\\(\\s*["\\'][^"\\']+["\\']', "Hardcoded like"),
    (r'\\.contains\\s*\\(\\s*["\\'][^"\\']+["\\']', "Hardcoded contains"),
    (r'\\.fillna\\s*\\(\\s*["\\'][^"\\']+["\\']', "Hardcoded fillna"),
    (r'\\.startswith\\s*\\(\\s*["\\'][^"\\']+["\\']', "Hardcoded startswith"),
    (r'\\.endswith\\s*\\(\\s*["\\'][^"\\']+["\\']', "Hardcoded endswith"),
]

COMPILED_PATTERNS = []
for p, d in HARDCODING_PATTERNS:
    try:
        COMPILED_PATTERNS.append((re.compile(p, re.IGNORECASE), d))
    except re.error:
        pass
''')
print("   ✅ src/fabric_notebook_compliance/patterns.py")

# ==============================================================================
# src/fabric_notebook_compliance/checker.py
# ==============================================================================
with open(os.path.join(BASE_DIR, "src", "fabric_notebook_compliance", "checker.py"), "w", encoding="utf-8") as f:
    f.write('''"""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)
''')
print("   ✅ src/fabric_notebook_compliance/checker.py")

print(f"""
{'='*60}
✅ PACKAGE CREATED SUCCESSFULLY!
{'='*60}

📁 Location: {BASE_DIR}

📋 Next steps:

1. Open terminal in this folder:
   cd "{BASE_DIR}"

2. Install build tools:
   pip install build twine

3. Build the package:
   python -m build

4. Upload to PyPI:
   twine upload dist/*
   - Username: __token__
   - Password: <your PyPI API token>

5. Done! Anyone can install:
   pip install fabric-notebook-compliance

{'='*60}
""")
