"""Report module - generates .cloven.md session reports for accumulated evidence.

These reports capture:
1. Initial assessment predictions
2. Actual execution outcomes
3. Accuracy analysis
4. Calibration insights

Reports accumulate over time as artifacts that can inform future development cycles.
"""

from __future__ import annotations

from datetime import datetime
from pathlib import Path

from cleave.core.file_utils import atomic_write_text
from cleave.core.metrics import (
    EFFORT_TO_COMPLEXITY,
    calculate_accuracy,
    get_metrics_summary,
)
from cleave.core.settings import generate_cloven_filename, load_settings
from cleave.core.yaml_utils import parse_yaml_simple


def generate_cloven_report(
    workspace_path: str | Path,
    aggregated: dict,
    conflicts: list,
    extracted_effort: list[dict],
    explicit_effort: str | None = None,
) -> str:
    """Generate a .cloven.md report for a completed cleave session.

    Args:
        workspace_path: Path to the workspace
        aggregated: Aggregated results from reunify
        conflicts: List of detected conflicts
        extracted_effort: List of effort extractions from tasks
        explicit_effort: Optional explicit effort override

    Returns:
        Markdown content for the report
    """
    workspace = Path(workspace_path)
    timestamp = datetime.now().isoformat()

    # Load manifest for assessment data
    manifest_path = workspace / "manifest.yaml"
    manifest = {}
    assessment = {}
    if manifest_path.exists():
        manifest = parse_yaml_simple(manifest_path.read_text())
        assessment = manifest.get("assessment", {})

    # Extract prediction data
    root_directive = manifest.get("root_directive", "[unknown]")
    predicted_complexity = assessment.get("complexity", 0.0)
    predicted_decision = assessment.get("decision", "unknown")
    pattern_matched = assessment.get("pattern", None)
    pattern_confidence = assessment.get("confidence", 0.0)
    # Note: systems is an int (count), modifiers is a list
    systems_count = assessment.get("systems", 0)
    modifiers_applied = assessment.get("modifiers", [])

    # Determine actual effort
    if explicit_effort:
        actual_effort = explicit_effort
        effort_source = "explicit override"
    elif extracted_effort:
        efforts = [e["inferred_effort"] for e in extracted_effort if e.get("inferred_effort")]
        if efforts:
            actual_effort = max(set(efforts), key=efforts.count)
            effort_source = "inferred from task results"
        else:
            actual_effort = "unknown"
            effort_source = "could not infer"
    else:
        actual_effort = "unknown"
        effort_source = "no data"

    # Calculate accuracy if we have both prediction and outcome
    accuracy_score = None
    accuracy_analysis = ""
    if actual_effort in EFFORT_TO_COMPLEXITY and predicted_complexity > 0:
        accuracy_score = calculate_accuracy(predicted_complexity, actual_effort)
        min_exp, max_exp = EFFORT_TO_COMPLEXITY[actual_effort]

        if accuracy_score >= 0.9:
            accuracy_analysis = "Prediction was accurate."
        elif accuracy_score >= 0.6:
            accuracy_analysis = f"Prediction was close. Expected range for '{actual_effort}' effort: {min_exp}-{max_exp}."
        else:
            if predicted_complexity < min_exp:
                accuracy_analysis = f"Under-predicted complexity. Predicted {predicted_complexity}, but '{actual_effort}' effort suggests {min_exp}-{max_exp}."
            else:
                accuracy_analysis = f"Over-predicted complexity. Predicted {predicted_complexity}, but '{actual_effort}' effort suggests {min_exp}-{max_exp}."

    # Build task outcomes table
    task_rows = []
    for task in aggregated.get("by_task", []):
        task_id = task.get("id", "?")
        status = task.get("status", "PENDING")
        files = task.get("file_count", 0)
        verified = "✓" if task.get("verified") else "✗"

        # Find matching effort extraction
        effort_match = next(
            (e for e in extracted_effort if str(task_id) in e.get("task_file", "")),
            None,
        )
        inferred = effort_match.get("inferred_effort", "-") if effort_match else "-"

        task_rows.append(f"| {task_id} | {status} | {files} | {verified} | {inferred} |")

    task_table = "\n".join(task_rows) if task_rows else "| - | - | - | - | - |"

    # Build conflict summary
    conflict_count = len(conflicts)
    if conflict_count == 0:
        conflict_summary = "No conflicts detected."
    else:
        conflict_types = {}
        for c in conflicts:
            ctype = c.type if hasattr(c, "type") else c.get("type", "unknown")
            conflict_types[ctype] = conflict_types.get(ctype, 0) + 1
        conflict_summary = ", ".join(f"{k}: {v}" for k, v in conflict_types.items())

    # Build calibration insight
    calibration_insight = _generate_calibration_insight(
        predicted_complexity=predicted_complexity,
        actual_effort=actual_effort,
        pattern_matched=pattern_matched,
        accuracy_score=accuracy_score,
        systems_count=systems_count,
        modifiers_applied=modifiers_applied,
    )

    # Format systems count and modifiers
    systems_str = str(systems_count) if systems_count else "0"
    modifiers_str = ", ".join(modifiers_applied) if modifiers_applied else "none applied"

    report = f"""# Cleave Session Report

**Generated:** {timestamp}
**Workspace:** {workspace_path}
**Directive:** {root_directive[:100]}{"..." if len(root_directive) > 100 else ""}

---

## Assessment Prediction

| Metric | Value |
|--------|-------|
| Pattern Matched | {pattern_matched or "none"} |
| Pattern Confidence | {pattern_confidence:.0%} |
| Predicted Complexity | {predicted_complexity:.1f} |
| Decision | {predicted_decision} |
| Systems Count | {systems_str} |
| Modifiers Applied | {modifiers_str} |

---

## Execution Outcome

| Metric | Value |
|--------|-------|
| Rollup Status | {aggregated.get("rollup_status", "UNKNOWN")} |
| Tasks Completed | {aggregated.get("task_count", 0)} |
| Conflicts Found | {conflict_count} |
| Conflict Types | {conflict_summary} |

### Task Breakdown

| Task | Status | Files | Verified | Effort |
|------|--------|-------|----------|--------|
{task_table}

---

## Accuracy Analysis

| Metric | Value |
|--------|-------|
| Actual Effort | {actual_effort} ({effort_source}) |
| Accuracy Score | {f"{accuracy_score:.0%}" if accuracy_score is not None else "N/A"} |

**Analysis:** {accuracy_analysis if accuracy_analysis else "Insufficient data for analysis."}

---

## Calibration Insight

{calibration_insight}

---

## Evidence for Future Sessions

This report can be provided as context for future cleave assessments to improve calibration:

```
Previous cleave on similar directive:
- Pattern: {pattern_matched or "none"}
- Predicted complexity: {predicted_complexity:.1f}
- Actual effort: {actual_effort}
- Accuracy: {f"{accuracy_score:.0%}" if accuracy_score is not None else "unknown"}
```

---

*Report generated by cleave v0.4.0*
"""

    return report


def _generate_calibration_insight(
    predicted_complexity: float,
    actual_effort: str,
    pattern_matched: str | None,
    accuracy_score: float | None,
    systems_count: int,
    modifiers_applied: list,
) -> str:
    """Generate actionable calibration insight based on prediction vs outcome."""
    insights = []

    if accuracy_score is None:
        return "Insufficient data to generate calibration insights. Ensure tasks are completed with result summaries."

    if accuracy_score >= 0.9:
        insights.append("**Assessment was well-calibrated.** No adjustment needed for this pattern.")
    elif accuracy_score >= 0.6:
        insights.append("**Assessment was reasonably accurate.** Minor refinements may improve precision.")
    else:
        # Significant miss - provide specific guidance
        if actual_effort == "high" and predicted_complexity < 4.0:
            insights.append(
                f"**Under-prediction detected.** Predicted {predicted_complexity:.1f} but actual effort was high."
            )
            if pattern_matched:
                insights.append(
                    f"Consider increasing `systems_base` for pattern '{pattern_matched}' by 1.0-1.5."
                )
            if systems_count < 3:
                insights.append(
                    "Few systems were detected. The complexity formula may need additional system indicators."
                )
        elif actual_effort == "low" and predicted_complexity > 2.5:
            insights.append(
                f"**Over-prediction detected.** Predicted {predicted_complexity:.1f} but actual effort was low."
            )
            if pattern_matched:
                insights.append(
                    f"Consider decreasing `systems_base` for pattern '{pattern_matched}' by 0.5-1.0."
                )
            if modifiers_applied:
                insights.append(
                    f"Modifiers applied ({', '.join(modifiers_applied)}) may be over-weighting complexity."
                )

    if not insights:
        insights.append("Review the assessment parameters if this pattern recurs with similar accuracy.")

    return "\n\n".join(insights)


def write_cloven_report(
    workspace_path: str | Path,
    aggregated: dict,
    conflicts: list,
    extracted_effort: list[dict],
    explicit_effort: str | None = None,
) -> Path:
    """Generate and write .cloven.md report based on settings.

    The report filename and location are controlled by ~/.cleave/settings.yaml:
    - cloven_report_path: Directory for reports (default: workspace)
    - cloven_report_naming: "descriptor", "timestamp", or "workspace"

    Returns:
        Path to the written report file
    """
    workspace = Path(workspace_path)

    # Load manifest to get directive for filename generation
    manifest_path = workspace / "manifest.yaml"
    directive = "[unknown directive]"
    if manifest_path.exists():
        try:
            manifest = parse_yaml_simple(manifest_path.read_text())
            directive = manifest.get("root_directive", directive)
        except Exception:
            pass

    report_content = generate_cloven_report(
        workspace_path=workspace_path,
        aggregated=aggregated,
        conflicts=conflicts,
        extracted_effort=extracted_effort,
        explicit_effort=explicit_effort,
    )

    # Generate filename based on settings
    settings = load_settings()
    report_path = generate_cloven_filename(
        directive=directive,
        workspace_path=workspace,
        settings=settings,
    )

    atomic_write_text(report_path, report_content)
    return report_path


def get_accumulated_reports(project_root: str | Path = ".") -> list[dict]:
    """Find all .cloven.md reports in a project for accumulated evidence.

    Args:
        project_root: Root directory to search

    Returns:
        List of report summaries with paths and key metrics
    """
    root = Path(project_root).resolve()
    reports = []

    # Search for .cloven.md files recursively
    for report_path in root.rglob(".cloven.md"):
        content = report_path.read_text()

        # Extract key metrics from report
        summary = {
            "path": str(report_path),
            "workspace": str(report_path.parent),
        }

        # Parse directive (rough extraction)
        if "**Directive:**" in content:
            start = content.find("**Directive:**") + len("**Directive:**")
            end = content.find("\n", start)
            summary["directive"] = content[start:end].strip()[:80]

        # Parse pattern
        if "| Pattern Matched |" in content:
            start = content.find("| Pattern Matched |") + len("| Pattern Matched |")
            end = content.find("|", start)
            summary["pattern"] = content[start:end].strip()

        # Parse accuracy
        if "| Accuracy Score |" in content:
            start = content.find("| Accuracy Score |") + len("| Accuracy Score |")
            end = content.find("|", start)
            accuracy_str = content[start:end].strip()
            if accuracy_str != "N/A":
                try:
                    summary["accuracy"] = float(accuracy_str.replace("%", "")) / 100
                except ValueError:
                    pass

        # Parse actual effort
        if "| Actual Effort |" in content:
            start = content.find("| Actual Effort |") + len("| Actual Effort |")
            end = content.find("(", start)
            if end == -1:
                end = content.find("|", start)
            summary["actual_effort"] = content[start:end].strip()

        reports.append(summary)

    return reports
