#!/usr/bin/env python3
"""
Docstring Generator using Claude Haiku 4.5

Generates Google-style docstrings for functions missing documentation.
Processes in batches of 8 functions per API call for cost efficiency.

Cost: $20-35 per run (depending on function count and complexity)
"""

import json
import os
import sys
from pathlib import Path
from typing import Any

from anthropic_helper import create_message


def load_missing_docs(docs_path: Path) -> list[dict[str, Any]]:
    """Load functions without docstrings from JSON."""
    try:
        with open(docs_path, "r") as f:
            docs = json.load(f)
        return docs
    except FileNotFoundError:
        print(f"::error::Docs file not found: {docs_path}", file=sys.stderr)
        sys.exit(1)
    except json.JSONDecodeError:
        print(f"::error::Invalid JSON in docs file: {docs_path}", file=sys.stderr)
        sys.exit(1)


def read_function_code(file_path: str, function_name: str, line: int) -> str:
    """Read the function code from file."""
    try:
        with open(file_path, "r") as f:
            lines = f.readlines()

        start_line = line - 1
        code_lines = []
        indent_level = None

        for i in range(start_line, len(lines)):
            line_text = lines[i]

            if indent_level is None:
                indent_level = len(line_text) - len(line_text.lstrip())

            current_indent = len(line_text) - len(line_text.lstrip())
            if i > start_line and current_indent <= indent_level and line_text.strip():
                if line_text.strip().startswith(("def ", "class ", "@")):
                    break

            code_lines.append(line_text)

            if len(code_lines) >= 50:
                break

        return "".join(code_lines)
    except Exception as e:
        return f"# Could not read function code: {e}"


def build_docstring_prompt(functions: list[dict[str, Any]]) -> str:
    """Build the prompt for generating docstrings."""
    functions_formatted = []

    for i, func in enumerate(functions, 1):
        code = read_function_code(func["file"], func["function"], func["line"])
        functions_formatted.append(f"""
### Function {i}: `{func["function"]}` in `{func["file"]}`

```python
{code}
```
""")

    functions_text = "\n".join(functions_formatted)

    return f"""You are a Python documentation expert. Generate clear, helpful Google-style docstrings for functions missing documentation.

## Functions to Document ({len(functions)} total)

{functions_text}

## Your Task

For each function, provide a complete Google-style docstring including:
1. **Summary line** - One sentence describing what the function does
2. **Args section** - Document each parameter with type and description
3. **Returns section** - Document return value with type and description
4. **Raises section** - Document exceptions raised (if applicable)
5. **Examples section** (optional) - Usage examples for complex functions

## Guidelines

- Summary line: Start with imperative verb (e.g., "Calculate", "Return", "Process")
- Be concise but informative
- Don't repeat the function name in the summary
- Use present tense for descriptions
- Include type information in Args/Returns even if type hints exist
- Only include Raises section if function actually raises exceptions
- Only include Example section for non-trivial functions

## Output Format

Respond with JSON:

```json
{{
  "functions_documented": [
    {{
      "file": "app/example.py",
      "function": "process_data",
      "line": 42,
      "docstring": "Process data items and return results.\\n\\nArgs:\\n    data: List of items to process.\\n    options: Optional configuration dict.\\n\\nReturns:\\n    Processed results as dict."
    }}
  ],
  "summary": {{
    "total_documented": 5,
    "functions_with_examples": 2,
    "functions_with_raises": 3
  }}
}}
```

Begin your analysis."""


def generate_docstrings_batch(
    functions: list[dict[str, Any]],
) -> dict[str, Any]:
    """Generate docstrings for a batch of functions using Claude Haiku 4.5."""
    prompt = build_docstring_prompt(functions)

    try:
        response = create_message(
            model="claude-haiku-4-5-20251001",
            max_tokens=4096,
            temperature=0,
            messages=[{"role": "user", "content": prompt}],
        )

        response_text = ""
        for block in response.content:
            if block.type == "text":
                response_text += block.text

        json_start = response_text.find("```json")
        if json_start != -1:
            json_start = response_text.find("\n", json_start) + 1
            json_end = response_text.find("```", json_start)
            response_text = response_text[json_start:json_end].strip()
        else:
            json_start = response_text.find("{")
            json_end = response_text.rfind("}") + 1
            if json_start != -1 and json_end > json_start:
                response_text = response_text[json_start:json_end]

        result = json.loads(response_text)

        result["_metadata"] = {
            "model": "claude-haiku-4-5-20251001",
            "input_tokens": response.usage.input_tokens,
            "output_tokens": response.usage.output_tokens,
            "total_tokens": response.usage.input_tokens + response.usage.output_tokens,
        }

        return result

    except json.JSONDecodeError:
        print("::error::Failed to parse JSON from Claude response", file=sys.stderr)
        print(f"Response text: {response_text[:500]}", file=sys.stderr)
        return {
            "functions_documented": [],
            "summary": {"total_documented": 0},
            "_metadata": {"error": "JSON parse error"},
        }
    except Exception as e:
        print(f"::error::API error: {e}", file=sys.stderr)
        return {
            "functions_documented": [],
            "summary": {"total_documented": 0},
            "_metadata": {"error": str(e)},
        }


def estimate_cost(input_tokens: int, output_tokens: int) -> float:
    """Estimate cost based on Claude Haiku 4.5 pricing."""
    input_cost = (input_tokens / 1_000_000) * 1
    output_cost = (output_tokens / 1_000_000) * 5
    return round(input_cost + output_cost, 2)


def main() -> None:
    """Main entry point for docstring generation."""
    docs_path = Path(sys.argv[1] if len(sys.argv) > 1 else "/tmp/missing-docs.json")
    output_path = Path(
        sys.argv[2] if len(sys.argv) > 2 else "/tmp/documentation-suggestions.json"
    )

    print(f"Loading functions without docstrings from {docs_path}...")
    functions = load_missing_docs(docs_path)

    if not functions:
        print("No functions need docstrings. Skipping generation.")
        result = {
            "functions_documented": [],
            "summary": {"total_documented": 0},
            "_metadata": {
                "model": "N/A",
                "input_tokens": 0,
                "output_tokens": 0,
                "total_tokens": 0,
            },
        }
    else:
        print(f"Generating docstrings for {len(functions)} functions...")

        all_results = []
        total_input_tokens = 0
        total_output_tokens = 0

        batch_size = 8
        for i in range(0, len(functions), batch_size):
            batch = functions[i : i + batch_size]
            print(f"Processing batch {i // batch_size + 1} ({len(batch)} functions)...")

            batch_result = generate_docstrings_batch(batch)
            all_results.extend(batch_result.get("functions_documented", []))

            metadata = batch_result.get("_metadata", {})
            total_input_tokens += metadata.get("input_tokens", 0)
            total_output_tokens += metadata.get("output_tokens", 0)

        result = {
            "functions_documented": all_results,
            "summary": {"total_documented": len(all_results)},
            "_metadata": {
                "model": "claude-haiku-4-5-20251001",
                "input_tokens": total_input_tokens,
                "output_tokens": total_output_tokens,
                "total_tokens": total_input_tokens + total_output_tokens,
            },
        }

    with open(output_path, "w") as f:
        json.dump(result, f, indent=2)

    print(f"Docstring generation complete. Results saved to {output_path}")

    summary = result.get("summary", {})
    metadata = result.get("_metadata", {})

    print("\n## Documentation Generation Results\n")
    print(f"- **Functions documented**: {summary.get('total_documented', 0)}")

    if metadata.get("input_tokens"):
        cost = estimate_cost(metadata["input_tokens"], metadata["output_tokens"])
        print(f"\n**Cost**: ${cost}")
        print(
            f"**Tokens**: {metadata['total_tokens']:,} ({metadata['input_tokens']:,} in + {metadata['output_tokens']:,} out)"
        )

        github_output = os.environ.get("GITHUB_OUTPUT", "")
        if github_output:
            with open(github_output, "a") as f:
                f.write(f"docstring_cost={cost}\n")


if __name__ == "__main__":
    main()
