"""
Clausi Docs Generator

Generates AI-powered documentation for codebases.
"""

import json
import os
import re
from pathlib import Path
from typing import Dict, List, Optional, Tuple
import requests

from rich.console import Console

console = Console()

# Default patterns to exclude
DEFAULT_EXCLUDES = [
    "**/node_modules/**",
    "**/__pycache__/**",
    "**/.git/**",
    "**/venv/**",
    "**/.venv/**",
    "**/dist/**",
    "**/build/**",
    "**/clausi/**",  # Exclude Clausi output folder (scans, docs, history)
    "**/*.pyc",
    "**/.env*",
    "**/coverage/**",
    "**/.idea/**",
    "**/.vscode/**",
    "**/*.min.js",
    "**/*.min.css",
    "**/package-lock.json",
    "**/yarn.lock",
    "**/poetry.lock",
]

# File extensions to include for analysis
CODE_EXTENSIONS = {
    ".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".go", ".rs",
    ".cpp", ".c", ".h", ".hpp", ".cs", ".rb", ".php", ".swift",
    ".kt", ".scala", ".vue", ".svelte"
}

CONFIG_EXTENSIONS = {
    ".json", ".yaml", ".yml", ".toml", ".ini", ".cfg"
}

DOC_EXTENSIONS = {
    ".md", ".rst", ".txt"
}


class DocsGenerator:
    """Generates documentation for a project using AI."""

    def __init__(
        self,
        project_path: Path,
        output_dir: Path,
        output_format: str = "md",
        exclude_patterns: List[str] = None,
        include_patterns: List[str] = None,
        provider: str = "claude",
        api_key: Optional[str] = None,
        model: Optional[str] = None
    ):
        self.project_path = Path(project_path).resolve()
        self.output_dir = Path(output_dir).resolve()
        self.output_format = output_format
        self.exclude_patterns = exclude_patterns or []
        self.include_patterns = include_patterns or []
        self.provider = provider
        self.api_key = api_key
        self.model = model

        # Will be populated by scan_project()
        self.files: List[Dict] = []
        self.project_structure: str = ""
        self.readme_content: str = ""
        self.package_info: Dict = {}

    def scan_project(self) -> Tuple[int, int]:
        """
        Scan the project and collect files for analysis.

        Returns:
            Tuple of (file_count, lines_of_code)
        """
        import pathspec

        # Build exclude patterns
        all_excludes = DEFAULT_EXCLUDES + self.exclude_patterns
        exclude_spec = pathspec.PathSpec.from_lines('gitwildmatch', all_excludes)

        # Build include patterns (if specified)
        include_spec = None
        if self.include_patterns:
            include_spec = pathspec.PathSpec.from_lines('gitwildmatch', self.include_patterns)

        # Collect files
        self.files = []
        total_loc = 0

        for file_path in self.project_path.rglob("*"):
            if not file_path.is_file():
                continue

            # Get relative path for pattern matching
            try:
                rel_path = file_path.relative_to(self.project_path)
            except ValueError:
                continue

            rel_path_str = str(rel_path).replace("\\", "/")

            # Check excludes
            if exclude_spec.match_file(rel_path_str):
                continue

            # Check includes (if specified)
            if include_spec and not include_spec.match_file(rel_path_str):
                continue

            # Check file extension
            suffix = file_path.suffix.lower()
            if suffix not in CODE_EXTENSIONS | CONFIG_EXTENSIONS | DOC_EXTENSIONS:
                continue

            # Read file content
            try:
                content = file_path.read_text(encoding='utf-8', errors='ignore')

                # Skip very large files (>50KB)
                if len(content) > 50000:
                    content = content[:50000] + "\n... [TRUNCATED]"

                # Count lines of code
                lines = content.count('\n') + (1 if content and not content.endswith('\n') else 0)

                self.files.append({
                    "path": rel_path_str,
                    "content": content,
                    "extension": suffix,
                    "size": len(content),
                    "lines": lines
                })
                total_loc += lines

            except Exception:
                continue

        # Extract README if exists
        for readme_name in ["README.md", "README.rst", "README.txt", "README"]:
            readme_path = self.project_path / readme_name
            if readme_path.exists():
                try:
                    self.readme_content = readme_path.read_text(encoding='utf-8')
                except Exception:
                    pass
                break

        # Extract package info
        self._extract_package_info()

        # Build project structure tree
        self.project_structure = self._build_tree()

        return len(self.files), total_loc

    def _extract_package_info(self):
        """Extract project name and info from package files."""
        # Try package.json
        package_json = self.project_path / "package.json"
        if package_json.exists():
            try:
                data = json.loads(package_json.read_text())
                self.package_info = {
                    "name": data.get("name", ""),
                    "description": data.get("description", ""),
                    "version": data.get("version", ""),
                    "type": "node"
                }
                return
            except Exception:
                pass

        # Try pyproject.toml
        pyproject = self.project_path / "pyproject.toml"
        if pyproject.exists():
            try:
                import tomllib
                data = tomllib.loads(pyproject.read_text())
                project = data.get("project", data.get("tool", {}).get("poetry", {}))
                self.package_info = {
                    "name": project.get("name", ""),
                    "description": project.get("description", ""),
                    "version": project.get("version", ""),
                    "type": "python"
                }
                return
            except Exception:
                pass

        # Try setup.py
        setup_py = self.project_path / "setup.py"
        if setup_py.exists():
            self.package_info = {"type": "python"}

        # Default to folder name
        if not self.package_info:
            self.package_info = {
                "name": self.project_path.name,
                "type": "unknown"
            }

    def _build_tree(self, max_depth: int = 3) -> str:
        """Build a directory tree string."""
        lines = []

        def add_dir(path: Path, prefix: str = "", depth: int = 0):
            if depth > max_depth:
                return

            # Get items, excluding hidden and common ignored
            items = []
            try:
                for item in sorted(path.iterdir()):
                    name = item.name
                    if name.startswith("."):
                        continue
                    if name in {"node_modules", "__pycache__", "venv", ".venv", "dist", "build"}:
                        continue
                    items.append(item)
            except PermissionError:
                return

            for i, item in enumerate(items):
                is_last = i == len(items) - 1
                connector = "└── " if is_last else "├── "

                if item.is_dir():
                    lines.append(f"{prefix}{connector}{item.name}/")
                    extension = "    " if is_last else "│   "
                    add_dir(item, prefix + extension, depth + 1)
                else:
                    lines.append(f"{prefix}{connector}{item.name}")

        lines.append(f"{self.project_path.name}/")
        add_dir(self.project_path)

        return "\n".join(lines[:50])  # Limit to 50 lines

    def get_hosted_estimate(self, api_url: str, token: str) -> Dict:
        """Get cost estimate from backend for hosted mode."""
        # Prepare files payload (limit to avoid huge request)
        files_payload = []
        total_size = 0
        max_size = 500000  # 500KB limit for estimate

        for f in self.files:
            if total_size + f["size"] > max_size:
                break
            files_payload.append({
                "path": f["path"],
                "content": f["content"]
            })
            total_size += f["size"]

        response = requests.post(
            f"{api_url}/api/clausi/docs/estimate",
            json={
                "files": files_payload,
                "project_name": self.package_info.get("name", self.project_path.name),
                "readme": self.readme_content[:5000] if self.readme_content else "",
                "structure": self.project_structure
            },
            headers={
                "Content-Type": "application/json",
                "X-Clausi-Key": token
            },
            timeout=30
        )

        if response.status_code != 200:
            raise Exception(f"Estimate failed: {response.text}")

        return response.json()

    def generate(self, api_url: str, token: str) -> Dict[str, str]:
        """
        Generate docs via backend API using async endpoint with polling.

        Uses async endpoint to avoid Cloudflare timeouts on long-running
        docs generation (which creates 4 documents sequentially).

        All modes (Clausi AI, Claude BYOK, OpenAI BYOK) go through the backend.
        The backend handles AI calls and keeps prompts proprietary.

        Headers sent:
            X-Clausi-Key: Always (for auth/credits)
            X-Anthropic-Key: If provider is claude (BYOK)
            X-OpenAI-Key: If provider is openai (BYOK)
        """
        import time
        from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeElapsedColumn

        # Prepare files payload
        files_payload = []
        total_size = 0
        max_size = 1000000  # 1MB limit

        for f in self.files:
            if total_size + f["size"] > max_size:
                break
            files_payload.append({
                "path": f["path"],
                "content": f["content"]
            })
            total_size += f["size"]

        # Build headers
        headers = {
            "Content-Type": "application/json",
            "X-Clausi-Key": token
        }

        # Add BYOK headers if using own API key
        if self.api_key:
            if self.provider == "claude":
                headers["X-Anthropic-Key"] = self.api_key
            elif self.provider == "openai":
                headers["X-OpenAI-Key"] = self.api_key

        # Start async docs generation job
        response = requests.post(
            f"{api_url}/api/clausi/docs/generate/async",
            json={
                "files": files_payload,
                "project_name": self.package_info.get("name", self.project_path.name),
                "readme": self.readme_content[:10000] if self.readme_content else "",
                "structure": self.project_structure,
                "format": self.output_format
            },
            headers=headers,
            timeout=30  # Just starting the job
        )

        if response.status_code == 402:
            raise Exception("Insufficient credits. Please purchase more at https://clausi.ai/dashboard")

        if response.status_code == 401:
            raise Exception("Invalid or expired token. Please run 'clausi login' to authenticate.")

        if response.status_code != 200:
            raise Exception(f"Generation failed: {response.text}")

        data = response.json()
        job_id = data.get("job_id")

        if not job_id:
            raise Exception("No job_id returned from server")

        # Poll for completion with progress display
        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            BarColumn(),
            TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
            TimeElapsedColumn(),
            console=console
        ) as progress:
            task = progress.add_task("[cyan]Generating documentation...", total=100)

            max_wait_time = 600  # 10 minutes max
            poll_interval = 3  # Check every 3 seconds
            start_time = time.time()

            while True:
                elapsed = time.time() - start_time
                if elapsed > max_wait_time:
                    raise Exception(f"Timeout waiting for docs generation (job: {job_id})")

                # Poll job status
                try:
                    status_response = requests.get(
                        f"{api_url}/api/clausi/jobs/{job_id}/status",
                        headers={"X-Clausi-Key": token},
                        timeout=10
                    )

                    if status_response.status_code != 200:
                        raise Exception(f"Failed to get job status: {status_response.text}")

                    status_data = status_response.json()
                    status = status_data.get("status", "unknown")
                    message = status_data.get("message", "")

                    # Get progress percentage from response
                    job_progress = status_data.get("progress", {})
                    progress_pct = job_progress.get("percentage", 0)
                    progress_msg = job_progress.get("message", message or "Processing...")

                    # Update progress bar
                    progress.update(task, completed=progress_pct, description=f"[cyan]{progress_msg}")

                    if status == "completed":
                        # Show completion
                        progress.update(task, completed=100, description="[green]Documentation complete!")

                        # Get result
                        result = status_data.get("result", {})
                        if result:
                            return result.get("docs", {})

                        # If result not in status, fetch from result endpoint
                        result_response = requests.get(
                            f"{api_url}/api/clausi/jobs/{job_id}/result",
                            headers={"X-Clausi-Key": token},
                            timeout=30
                        )
                        if result_response.status_code == 200:
                            result_data = result_response.json()
                            return result_data.get("docs", {})
                        else:
                            raise Exception(f"Failed to get job result: {result_response.text}")

                    elif status == "failed":
                        error = status_data.get("error", "Unknown error")
                        raise Exception(f"Docs generation failed: {error}")

                    elif status in ["pending", "running"]:
                        # Still processing, continue polling
                        pass
                    else:
                        console.print(f"[yellow]Unknown status: {status}[/yellow]")

                except requests.exceptions.Timeout:
                    console.print("[yellow]Timeout checking status, retrying...[/yellow]")
                except requests.exceptions.ConnectionError:
                    console.print("[yellow]Connection error, retrying...[/yellow]")

                time.sleep(poll_interval)

    def save_docs(self, docs: Dict[str, str]):
        """Save generated docs to output directory."""
        # Create output directory
        self.output_dir.mkdir(parents=True, exist_ok=True)

        # Write each doc
        for filename, content in docs.items():
            filepath = self.output_dir / filename
            filepath.write_text(content, encoding='utf-8')

        # Create a simple README in the docs folder
        docs_readme = self.output_dir.parent / "README.md"
        if not docs_readme.exists():
            docs_readme.write_text(
                "# Clausi Docs\n\n"
                "Auto-generated documentation for this project.\n\n"
                "## Contents\n\n"
                "- `latest/` - Most recent documentation\n\n"
                "## Viewing\n\n"
                "Open the markdown files in your IDE or push to GitHub to view rendered.\n\n"
                "## Regenerating\n\n"
                "```bash\n"
                "clausi docs generate\n"
                "```\n",
                encoding='utf-8'
            )
