"""Workspace module - workspace generation and context reconstruction."""

from __future__ import annotations

import re
from datetime import datetime
from pathlib import Path

from cleave.core.assessment import PATTERNS, assess_directive, match_pattern
from cleave.core.file_utils import atomic_write_text
from cleave.core.permissions import infer_permissions
from cleave.core.yaml_utils import parse_yaml_simple, to_yaml

# Maximum allowed depth for cleave recursion (defense against resource exhaustion)
MAX_ALLOWED_DEPTH = 10


def generate_workspace_name(directive: str, base_dir: str = ".") -> str:
    """Generate unique workspace directory name from directive.

    Creates a human-readable slug from the directive and ensures uniqueness
    by checking for existing directories. Uses incremental suffixes if needed.

    Args:
        directive: The task directive to slugify
        base_dir: Parent directory to check for existing workspaces

    Returns:
        Unique workspace path like ".cleave-add-jwt-auth" or ".cleave-add-jwt-auth-2"

    Example:
        >>> generate_workspace_name("Add JWT authentication with refresh tokens")
        '.cleave-add-jwt-auth'
    """
    # Slugify: lowercase, replace spaces/special chars with hyphens, truncate
    slug = directive.lower()
    slug = re.sub(r"[^\w\s-]", "", slug)  # Remove special chars
    slug = re.sub(r"[\s_]+", "-", slug)  # Spaces/underscores -> hyphens
    slug = re.sub(r"-+", "-", slug)  # Collapse multiple hyphens
    slug = slug.strip("-")  # Trim leading/trailing hyphens

    # Truncate to reasonable length (40 chars)
    if len(slug) > 40:
        slug = slug[:40].rstrip("-")

    # Ensure uniqueness
    base = Path(base_dir)
    workspace_name = f".cleave-{slug}"
    candidate = base / workspace_name

    if not candidate.exists():
        return workspace_name

    # Find next available suffix
    counter = 2
    while (base / f"{workspace_name}-{counter}").exists():
        counter += 1

    return f"{workspace_name}-{counter}"

# =============================================================================
# INTENT SCAFFOLDS - Pattern-specific templates
# =============================================================================

INTENT_SCAFFOLDS = {
    "full_stack_crud": {
        "success_criteria": [
            "Users can create new {entity} via UI form",
            "Users can view {entity} list and details",
            "Users can update existing {entity}",
            "Users can delete {entity} with confirmation",
            "All operations persist to database correctly",
        ],
        "constraints": [
            "Use existing database schema conventions",
            "Follow established API patterns",
            "Maintain consistent UI/UX with existing pages",
        ],
        "out_of_scope": [
            "Bulk operations",
            "Advanced filtering/search",
            "Export functionality",
        ],
    },
    "authentication": {
        "success_criteria": [
            "Users can register with valid credentials",
            "Users can log in and receive auth token",
            "Protected routes reject unauthenticated requests",
            "Tokens expire and refresh correctly",
            "Passwords are securely hashed",
        ],
        "constraints": [
            "Use industry-standard hashing (bcrypt/argon2)",
            "Tokens must be signed and validated",
            "No plaintext credentials in logs or responses",
        ],
        "out_of_scope": [
            "Social login (OAuth providers)",
            "Multi-factor authentication",
            "Password recovery flow",
        ],
    },
    "external_integration": {
        "success_criteria": [
            "API client successfully authenticates with provider",
            "Core operations complete without errors",
            "Webhooks are received and processed",
            "Failures are logged and retried appropriately",
            "Transaction records are stored for audit",
        ],
        "constraints": [
            "Handle rate limits gracefully",
            "Implement idempotency for webhooks",
            "Never expose API keys in client code",
        ],
        "out_of_scope": [
            "Multi-provider support",
            "Admin dashboard for monitoring",
            "Historical data migration",
        ],
    },
    "database_migration": {
        "success_criteria": [
            "Migration applies cleanly to dev/staging/prod",
            "Rollback script works correctly",
            "No data loss during migration",
            "Performance impact is acceptable",
        ],
        "constraints": [
            "Must be reversible (rollback plan required)",
            "Zero-downtime if production",
            "Test on copy of production data first",
        ],
        "out_of_scope": [
            "Application code changes (separate task)",
            "Performance optimization (beyond index)",
        ],
    },
    "performance_optimization": {
        "success_criteria": [
            "Target latency SLA is met (e.g., p95 < 100ms)",
            "Cache hit rate exceeds threshold",
            "No stale data served beyond TTL",
            "Monitoring dashboards show improvement",
        ],
        "constraints": [
            "Cache invalidation must be consistent",
            "Graceful degradation if cache unavailable",
            "Memory usage within limits",
        ],
        "out_of_scope": [
            "Infrastructure scaling",
            "Database query optimization (separate task)",
            "CDN configuration",
        ],
    },
    "breaking_api_change": {
        "success_criteria": [
            "New versioned endpoint works correctly",
            "Old endpoint returns deprecation warning",
            "All clients updated to new endpoint",
            "Documentation updated for both versions",
        ],
        "constraints": [
            "Maintain backward compatibility for deprecation period",
            "Clearly document migration path",
            "Version must be in URL or header",
        ],
        "out_of_scope": [
            "Automatic client migration",
            "Breaking changes to data format",
        ],
    },
    "simple_refactor": {
        "success_criteria": [
            "All tests pass after refactor",
            "No functional changes (behavior preserved)",
            "Code follows project conventions",
            "All call sites updated",
        ],
        "constraints": [
            "Mechanical changes only - no logic changes",
            "Single commit for atomic change",
            "Update imports/references consistently",
        ],
        "out_of_scope": [
            "Functional improvements",
            "Performance optimization",
            "Adding new tests",
        ],
    },
    "bug_fix": {
        "success_criteria": [
            "Bug is reproducible before fix",
            "Fix resolves the reported issue",
            "No regressions introduced",
            "Regression test added to prevent recurrence",
        ],
        "constraints": [
            "Minimal scope - fix only what's broken",
            "Preserve existing behavior except bug",
            "Document root cause in commit message",
        ],
        "out_of_scope": [
            "Related refactoring (separate task)",
            "Performance improvements",
            "Feature additions",
        ],
    },
    "refactor": {
        "success_criteria": [
            "New implementation passes all existing tests",
            "Behavior is preserved (no functional changes)",
            "All call sites updated to use new implementation",
            "Old implementation removed (no dead code)",
        ],
        "constraints": [
            "Maintain external API compatibility",
            "Run full test suite before/after",
            "Document migration path for any breaking changes",
        ],
        "out_of_scope": [
            "New features or functionality",
            "Changing external contracts",
            "Performance optimization (unless that's the goal)",
        ],
    },
}


def generate_intent_scaffold(directive: str, pattern_id: str | None) -> dict:
    """Generate pattern-aware intent scaffold.

    If pattern is matched, uses pattern-specific templates.
    Otherwise, provides generic TODOs.
    """
    goal = directive.split(".")[0] if "." in directive else directive
    if len(goal) > 100:
        goal = goal[:100] + "..."

    if pattern_id and pattern_id in INTENT_SCAFFOLDS:
        scaffold = INTENT_SCAFFOLDS[pattern_id]
        return {
            "goal": goal,
            "success_criteria": scaffold["success_criteria"],
            "constraints": scaffold["constraints"],
            "out_of_scope": scaffold["out_of_scope"],
        }
    else:
        return {
            "goal": goal,
            "success_criteria": [
                "TODO: Define testable outcome 1",
                "TODO: Define testable outcome 2",
                "TODO: Define testable outcome 3",
            ],
            "constraints": ["TODO: Add hard constraints"],
            "out_of_scope": ["TODO: Add explicit exclusions"],
        }


def generate_manifest(
    directive: str,
    children: list[str],
    mode: str = "lean",
    threshold: float = 2.0,
    validate: bool = True,
    max_depth: int = 5,
    parallel: bool = True,
    parent_manifest: dict | None = None,
    node_id: str = "root",
    depth: int = 0,
    include_permissions: bool = False,
) -> str:
    """Generate manifest.yaml content with pattern-aware scaffolding.

    Args:
        directive: The directive to decompose
        children: List of child labels
        mode: lean or robust
        threshold: Complexity threshold for cleaving
        validate: Add validation step
        max_depth: Maximum recursion depth
        parallel: Execute children in parallel
        parent_manifest: Parent's manifest for nested cleaves (inherits intent)
        node_id: This node's ID in the tree (e.g., "0.1.2")
        depth: Current depth in the tree (0 = root)
        include_permissions: Include inferred permissions in manifest
    """
    assessment = assess_directive(directive, threshold, validate)

    # Get pattern ID for intent scaffolding
    match = match_pattern(directive)
    pattern_id = match.pattern_id if match else None

    # Inherit intent from parent (immutable) or generate fresh
    if parent_manifest and "intent" in parent_manifest:
        intent = parent_manifest["intent"]  # IMMUTABLE - copied verbatim
    else:
        intent = generate_intent_scaffold(directive, pattern_id)

    # Build ancestry chain
    parent_chain = []
    if parent_manifest and "ancestry" in parent_manifest:
        # Copy parent's chain and add parent as new entry
        parent_chain = parent_manifest["ancestry"].get("parent_chain", [])[:]
        parent_chain.append(
            {
                "node_id": parent_manifest["ancestry"].get("node_id", "unknown"),
                "label": parent_manifest.get("branch_label", "parent"),
                "depth": parent_manifest["ancestry"].get("depth", depth - 1),
                "directive_summary": parent_manifest.get("root_directive", directive)[:100] + "...",
            }
        )

    # Inherit root_directive from parent or use this directive
    root_directive = directive
    if parent_manifest and "root_directive" in parent_manifest:
        root_directive = parent_manifest["root_directive"]

    manifest = {
        "version": 1,
        "mode": mode,
        "threshold": threshold,
        "validate": validate,
        "max_splits": len(children),
        "max_depth": max_depth,
        "max_retries": 2,
        "parallel": parallel,
        "halt_on_failure": False,
        "root_directive": root_directive,
        "branch_directive": directive if depth > 0 else None,  # Only for non-root
        "branch_label": children[0] if children else "task",  # Label for this branch
        "intent": intent,
        "assessment": {
            "complexity": assessment.complexity,
            "systems": assessment.systems,
            "modifiers": assessment.modifiers,
            "method": assessment.method,
            "pattern": assessment.pattern,
            "confidence": assessment.confidence,
            "decision": assessment.decision,
            "reasoning": assessment.reasoning,
        },
        "ancestry": {
            "depth": depth,
            "node_id": node_id,
            "parent_chain": parent_chain,
            "sibling_count": len(children),
            "remaining_budget": max_depth - depth,
        },
        "children": [{"id": i, "label": label, "depth": depth + 1} for i, label in enumerate(children)],
        "created_at": datetime.now().isoformat(),
    }

    # Add inferred permissions if requested
    if include_permissions:
        perms = infer_permissions(directive, pattern_id)
        manifest["inferred_permissions"] = perms

    return to_yaml(manifest)


def generate_task_file(
    task_id: int,
    label: str,
    mission: str,
    siblings: list[str],
    depth: int = 1,
    tdd: bool = True,
) -> str:
    """Generate N-task.md content (slim template, ~50 lines rendered).

    Args:
        task_id: Numeric ID for this task
        label: Human-readable task label
        mission: Task mission statement
        siblings: List of sibling task labels
        depth: Depth in cleave tree
        tdd: Include TDD workflow marker (default: True)
    """
    sibling_refs = [i for i in range(len(siblings)) if i != task_id]
    sibling_list = ", ".join([f"{i}:{siblings[i]}" for i in sibling_refs]) or "none"

    # Compact TDD marker
    tdd_marker = "tdd: true  # Red→Green→Refactor" if tdd else "tdd: false"

    return f"""---
task_id: {task_id}
depth: {depth}
parent: ../manifest.yaml
siblings: [{sibling_list}]
{tdd_marker}
---

# Task {task_id}: {label}

## Context Files (Review Before Starting)

- [ ] Read `manifest.yaml` for root intent and success criteria
- [ ] Check `siblings.yaml` for sibling coordination and dependencies
- [ ] Review ancestry chain if at depth > 0

## Mission

{mission}

## Scope

**In:** (define inclusions)
**Out:** (define exclusions)
**Depends on:** (sibling interfaces needed)
**Provides:** (interfaces to siblings)

## Result

**Status:** PENDING

**Summary:**

**Artifacts:**

**Decisions:**

**Verification:**
- Command: ``
- Output:
- Edge cases:

## Alignment Check (Required)

Before marking COMPLETE, verify:

- **Root Goal**: Does this result fulfill the original goal from manifest.yaml?
- **Success Criteria**: Which criteria from manifest.yaml does this satisfy?
- **Constraints**: Have I violated any constraints from manifest.yaml?
- **Scope Adherence**: Did I stay within my task scope without encroaching on siblings?

**Alignment Summary:** [REQUIRED - explain how this task aligns with root intent]
"""


def generate_siblings_yaml(children: list[str]) -> str:
    """Generate siblings.yaml."""
    siblings_data = {
        "coordination": {
            "created_at": datetime.now().isoformat(),
            "parent_node": "root",
            "depth": 1,
            "mode": "parallel",
        },
        "siblings": [
            {
                "id": i,
                "label": label,
                "focus": f"TODO: Describe {label} focus",
                "status": "pending",
                "exports": {"interfaces": [], "artifacts": []},
                "dependencies": [],
                "file_claims": [],
            }
            for i, label in enumerate(children)
        ],
        "conflicts": [],
        "messages": [],
    }

    return to_yaml(siblings_data)


def validate_intent_completeness(intent: dict) -> tuple[bool, list[str]]:
    """Check if intent scaffold has TODOs or empty fields (incomplete).

    Args:
        intent: Intent dict with goal, success_criteria, constraints, out_of_scope

    Returns:
        Tuple of (is_complete, list of fields with TODOs or empty values)
    """
    import sys

    todo_fields = []

    # Check goal is non-empty and has no TODO
    goal = intent.get("goal", "")
    if not goal or not goal.strip() or "TODO" in goal:
        todo_fields.append("goal")

    # Check success_criteria list is non-empty and has no TODOs
    success_criteria = intent.get("success_criteria", [])
    if not success_criteria or any("TODO" in str(item) for item in success_criteria):
        todo_fields.append("success_criteria")

    # Check constraints list is non-empty and has no TODOs
    constraints = intent.get("constraints", [])
    if not constraints or any("TODO" in str(item) for item in constraints):
        todo_fields.append("constraints")

    # out_of_scope can be empty (legitimately nothing excluded)
    # Only flag if it has TODOs
    out_of_scope = intent.get("out_of_scope", [])
    if any("TODO" in str(item) for item in out_of_scope):
        todo_fields.append("out_of_scope")

    return (len(todo_fields) == 0, todo_fields)


def init_workspace(
    directive: str,
    children: list[str],
    output_dir: str | None = None,
    mode: str = "lean",
    threshold: float = 2.0,
    max_depth: int = 5,
    parent_manifest_path: str | None = None,
    node_id: str = "root",
    depth: int = 0,
    include_permissions: bool = False,
    tdd: bool = True,
    validate_intent: bool = False,
    require_complete_intent: bool = False,
) -> dict:
    """Initialize cleave workspace.

    Args:
        directive: The directive to decompose
        children: List of child task labels
        output_dir: Output directory for workspace files. If None, auto-generates
            a unique name from the directive (e.g., ".cleave-add-jwt-auth").
        mode: lean or robust
        threshold: Complexity threshold
        max_depth: Maximum recursion depth
        parent_manifest_path: Path to parent's manifest.yaml (for nested cleaves)
        node_id: This node's ID in the tree (e.g., "0.1.2")
        depth: Current depth in the tree (0 = root)
        include_permissions: Include inferred permissions in manifest
        tdd: Include TDD workflow in task files (default: True)
        validate_intent: Warn if intent contains TODOs (default: False)
        require_complete_intent: Error if intent contains TODOs (default: False)

    Returns:
        dict with created files, or error dict if MAX_DEPTH exceeded or intent incomplete
    """
    # Auto-generate workspace name if not provided
    if output_dir is None:
        output_dir = generate_workspace_name(directive)

    # Intent validation (check before creating workspace)
    if validate_intent or require_complete_intent:
        # Generate intent to check for TODOs
        match = match_pattern(directive)
        pattern_id = match.pattern_id if match else None
        intent = generate_intent_scaffold(directive, pattern_id)

        is_complete, todo_fields = validate_intent_completeness(intent)

        if not is_complete:
            if require_complete_intent:
                return {
                    "error": "INCOMPLETE_INTENT",
                    "message": f"Intent contains TODOs in: {', '.join(todo_fields)}",
                    "guidance": "Either use a recognized pattern (authentication, CRUD, etc.) "
                    "or manually edit manifest.yaml after generation to replace TODOs",
                    "todo_fields": todo_fields,
                }
            elif validate_intent:
                # Warn but don't block
                import sys

                print(
                    f"⚠️  WARNING: Intent contains TODOs in: {', '.join(todo_fields)}",
                    file=sys.stderr,
                )
                print(
                    "   You will need to edit manifest.yaml to replace TODOs with actual intent.",
                    file=sys.stderr,
                )

    # MAX_DEPTH enforcement
    if depth >= max_depth:
        return {
            "error": "MAX_DEPTH_EXCEEDED",
            "message": f"Cannot cleave at depth {depth}: max_depth is {max_depth}",
            "guidance": "Either increase max_depth, or execute this task atomically without cleaving",
        }

    # Load parent manifest if provided (for nested cleaves)
    parent_manifest = None
    if parent_manifest_path:
        parent_path = Path(parent_manifest_path)
        if parent_path.exists():
            parent_manifest = parse_yaml_simple(parent_path.read_text())
            # Inherit max_depth from parent, with bounds checking to prevent resource exhaustion
            if "max_depth" in parent_manifest:
                inherited_depth = parent_manifest["max_depth"]
                # Cap inherited max_depth at MAX_ALLOWED_DEPTH (defense against malicious manifests)
                max_depth = min(int(inherited_depth), MAX_ALLOWED_DEPTH)
            # Re-check depth after loading parent's max_depth
            if depth >= max_depth:
                return {
                    "error": "MAX_DEPTH_EXCEEDED",
                    "message": f"Cannot cleave at depth {depth}: inherited max_depth is {max_depth}",
                    "guidance": "Execute this task atomically without further cleaving",
                }

    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    created_files = {}

    # manifest.yaml
    manifest_content = generate_manifest(
        directive=directive,
        children=children,
        mode=mode,
        threshold=threshold,
        max_depth=max_depth,
        parent_manifest=parent_manifest,
        node_id=node_id,
        depth=depth,
        include_permissions=include_permissions,
    )
    manifest_path = output_path / "manifest.yaml"
    atomic_write_text(manifest_path, manifest_content)
    created_files["manifest.yaml"] = str(manifest_path)

    # siblings.yaml
    siblings_content = generate_siblings_yaml(children)
    siblings_path = output_path / "siblings.yaml"
    atomic_write_text(siblings_path, siblings_content)
    created_files["siblings.yaml"] = str(siblings_path)

    # Task files (children are at depth + 1)
    child_depth = depth + 1
    for i, label in enumerate(children):
        task_content = generate_task_file(
            task_id=i,
            label=label,
            mission=f"TODO: Define specific mission for {label}",
            siblings=children,
            depth=child_depth,
            tdd=tdd,
        )
        task_path = output_path / f"{i}-task.md"
        atomic_write_text(task_path, task_content)
        created_files[f"{i}-task.md"] = str(task_path)

    # metrics.yaml
    metrics = {
        "telemetry": {
            "created_at": datetime.now().isoformat(),
            "ancestry_access_count": 0,
            "sibling_access_count": 0,
            "intent_reference_count": 0,
            "cache_hits": 0,
            "cache_misses": 0,
            "fast_path_hits": 0,
            "sequential_fallback": 0,
        }
    }
    metrics_path = output_path / "metrics.yaml"
    atomic_write_text(metrics_path, to_yaml(metrics))
    created_files["metrics.yaml"] = str(metrics_path)

    # Include the actual workspace directory used (important when auto-generated)
    created_files["workspace"] = output_dir

    return created_files


def reconstruct_context(manifest_path: str) -> dict:
    """Reconstruct full context from manifest."""
    path = Path(manifest_path)
    if not path.exists():
        return {"error": f"Manifest not found: {manifest_path}"}

    content = path.read_text()
    manifest = parse_yaml_simple(content)

    context = {
        "root_intent": manifest.get("intent", {}),
        "ancestry": manifest.get("ancestry", {}),
        "current_node": manifest.get("ancestry", {}).get("node_id", "unknown"),
        "depth": manifest.get("ancestry", {}).get("depth", 0),
        "parent_chain": manifest.get("ancestry", {}).get("parent_chain", []),
        "constraints": manifest.get("intent", {}).get("constraints", []),
        "assessment": manifest.get("assessment", {}),
        "children": manifest.get("children", []),
    }

    siblings_path = path.parent / "siblings.yaml"
    if siblings_path.exists():
        context["siblings"] = parse_yaml_simple(siblings_path.read_text())

    return context
