"""
Task delegation system for the terminal broker.

Implements a request/response pattern where one terminal can delegate work
to another. Tasks are stored as individual JSON files in state/broker/tasks/.

Functions are sync (no async) for use from CLI and MCP tool wrappers.
"""

import json
import os
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

from brynq.server import (
    CHANNELS_DIR,
    SESSION_ID,
    SESSION_NAME,
    PLATFORM,
    _append_jsonl_sync,
)

# ── Paths ──────────────────────────────────────────────────────────────────

BROKER_DIR = CHANNELS_DIR.parent
TASKS_DIR = BROKER_DIR / "tasks"
TASKS_DIR.mkdir(parents=True, exist_ok=True)

VALID_PRIORITIES = ("low", "normal", "high", "urgent")
VALID_STATUSES = ("pending", "accepted", "completed", "failed")


# ── Internal helpers ───────────────────────────────────────────────────────


def _read_task(task_id: str) -> Optional[dict]:
    """Read a task file by ID. Returns None if not found or corrupt."""
    path = TASKS_DIR / f"{task_id}.json"
    if not path.exists():
        return None
    try:
        return json.loads(path.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return None


def _write_task(task: dict) -> None:
    """Write a task dict to its JSON file. Atomic via temp + replace."""
    path = TASKS_DIR / f"{task['task_id']}.json"
    tmp = path.with_suffix(".tmp")
    tmp.write_text(json.dumps(task, indent=2, default=str), encoding="utf-8")
    os.replace(str(tmp), str(path))


def _notify_channel(channel: str, message: str) -> None:
    """Post a task notification to the broker channel."""
    entry = {
        "id": uuid.uuid4().hex[:12],
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "session_id": SESSION_ID,
        "session_name": SESSION_NAME,
        "platform": PLATFORM,
        "channel": channel,
        "msg_type": "task",
        "message": message,
    }
    path = CHANNELS_DIR / f"{channel}.jsonl"
    _append_jsonl_sync(path, entry)


# ── Public API ─────────────────────────────────────────────────────────────


def create_task(
    channel: str,
    title: str,
    description: str,
    priority: str = "normal",
) -> str:
    """Create a new task and post a notification to the channel.

    Args:
        channel: Broker channel to post the task notification to.
        title: Short summary of the work to be done.
        description: Detailed description of the task.
        priority: One of 'low', 'normal', 'high', 'urgent'.

    Returns:
        The generated task_id.

    Raises:
        ValueError: If priority is not a valid value.
    """
    if priority not in VALID_PRIORITIES:
        raise ValueError(
            f"Invalid priority '{priority}'. Must be one of: {', '.join(VALID_PRIORITIES)}"
        )

    task_id = uuid.uuid4().hex[:12]
    now = datetime.now(timezone.utc).isoformat()

    task = {
        "task_id": task_id,
        "channel": channel,
        "status": "pending",
        "requester_session": SESSION_ID,
        "requester_name": f"{SESSION_NAME}@{PLATFORM}",
        "assignee_session": None,
        "assignee_name": None,
        "title": title,
        "description": description,
        "created": now,
        "accepted_at": None,
        "completed_at": None,
        "result": None,
        "priority": priority,
    }

    _write_task(task)
    _notify_channel(
        channel,
        f"[TASK CREATED] [{priority.upper()}] {title} (id: {task_id})",
    )

    return task_id


def list_tasks(
    channel: Optional[str] = None,
    status: Optional[str] = None,
) -> list[dict]:
    """List tasks with optional filters.

    Args:
        channel: Filter by channel name. None returns all channels.
        status: Filter by status ('pending', 'accepted', 'completed', 'failed').
            None returns all statuses.

    Returns:
        List of task dicts matching the filters, sorted by creation time
        (newest first).

    Raises:
        ValueError: If status is not a valid value.
    """
    if status is not None and status not in VALID_STATUSES:
        raise ValueError(
            f"Invalid status '{status}'. Must be one of: {', '.join(VALID_STATUSES)}"
        )

    tasks = []
    for f in TASKS_DIR.glob("*.json"):
        task = _read_task(f.stem)
        if task is None:
            continue
        if channel is not None and task.get("channel") != channel:
            continue
        if status is not None and task.get("status") != status:
            continue
        tasks.append(task)

    tasks.sort(key=lambda t: t.get("created", ""), reverse=True)
    return tasks


def accept_task(task_id: str) -> dict:
    """Mark a task as accepted by the current session.

    Args:
        task_id: The task to accept.

    Returns:
        The updated task dict.

    Raises:
        FileNotFoundError: If the task does not exist.
        ValueError: If the task is not in 'pending' status.
    """
    task = _read_task(task_id)
    if task is None:
        raise FileNotFoundError(f"Task '{task_id}' not found")

    if task["status"] not in ("pending", "delegated"):
        raise ValueError(
            f"Cannot accept task in '{task['status']}' status (must be 'pending' or 'delegated')"
        )

    task["status"] = "accepted"
    task["assignee_session"] = SESSION_ID
    task["assignee_name"] = f"{SESSION_NAME}@{PLATFORM}"
    task["accepted_at"] = datetime.now(timezone.utc).isoformat()

    _write_task(task)
    
    # Phase 25: ERP Resource Allocation
    try:
        try:
            from brynq.erp import allocate_task as erp_allocate
        except ImportError:
            from brynq._future.erp import allocate_task as erp_allocate
        erp_allocate(
            task_id=task_id, 
            estimated_tokens=5000, 
            estimated_hours=0.5, 
            session_id=SESSION_ID
        )
    except ImportError:
        pass
    except Exception as e:
        import logging
        logging.getLogger(__name__).warning(f"ERP allocation failed: {e}")

    _notify_channel(
        task["channel"],
        f"[TASK ACCEPTED] {task['title']} (id: {task_id}) — accepted by {SESSION_NAME}@{PLATFORM}",
    )

    return task


def complete_task(task_id: str, result: str) -> dict:
    """Mark a task as completed with a result message.

    Args:
        task_id: The task to complete.
        result: Summary of what was done or the output.

    Returns:
        The updated task dict.

    Raises:
        FileNotFoundError: If the task does not exist.
        ValueError: If the task is not in 'accepted' status.
    """
    task = _read_task(task_id)
    if task is None:
        raise FileNotFoundError(f"Task '{task_id}' not found")

    if task["status"] != "accepted":
        raise ValueError(
            f"Cannot complete task in '{task['status']}' status (must be 'accepted')"
        )

    task["status"] = "completed"
    task["completed_at"] = datetime.now(timezone.utc).isoformat()
    task["result"] = result

    _write_task(task)
    
    erp_data = None
    # Phase 25: ERP Resource Release
    try:
        try:
            from brynq.erp import complete_task as erp_complete
        except ImportError:
            from brynq._future.erp import complete_task as erp_complete
        
        # Estimate actual tokens based on result length (rough heuristic)
        actual_tokens_est = len(result) * 2 + 5000 
        
        erp_data = erp_complete(
            task_id=task_id, 
            actual_tokens=actual_tokens_est,
            actual_hours=0.5, # In a real implementation this would calculate diff from accepted_at
            session_id=SESSION_ID
        )
    except ImportError:
        pass
    except Exception as e:
        import logging
        logging.getLogger(__name__).warning(f"ERP completion failed: {e}")

    # Phase 102: The Feedback Loop (Outcome Learning & Knowledge Distillation)
    try:
        from brynq.learning_feedback import on_swarm_complete
        on_swarm_complete(SESSION_ID, task, erp_data)
    except Exception as e:
        import logging
        logging.getLogger(__name__).warning(f"Feedback loop failed: {e}")

    _notify_channel(
        task["channel"],
        f"[TASK COMPLETED] {task['title']} (id: {task_id}) — {result[:200]}",
    )

    return task


def fail_task(task_id: str, reason: str) -> dict:
    """Mark a task as failed with a reason.

    Args:
        task_id: The task to mark as failed.
        reason: Why the task failed.

    Returns:
        The updated task dict.

    Raises:
        FileNotFoundError: If the task does not exist.
        ValueError: If the task is not in 'pending' or 'accepted' status.
    """
    task = _read_task(task_id)
    if task is None:
        raise FileNotFoundError(f"Task '{task_id}' not found")

    if task["status"] not in ("pending", "accepted"):
        raise ValueError(
            f"Cannot fail task in '{task['status']}' status (must be 'pending' or 'accepted')"
        )

    task["status"] = "failed"
    task["completed_at"] = datetime.now(timezone.utc).isoformat()
    task["result"] = reason

    _write_task(task)
    _notify_channel(
        task["channel"],
        f"[TASK FAILED] {task['title']} (id: {task_id}) — {reason[:200]}",
    )

    return task


def task_status(task_id: str) -> dict:
    """Get full details for a task.

    Args:
        task_id: The task to look up.

    Returns:
        The full task dict.

    Raises:
        FileNotFoundError: If the task does not exist.
    """
    task = _read_task(task_id)
    if task is None:
        raise FileNotFoundError(f"Task '{task_id}' not found")
    return task



def submit_for_review(task_id: str, deliverable: str = "") -> dict:
    """Submit a task for peer review by another agent."""
    task = _read_task(task_id)
    if not task:
        return {"error": f"Task {task_id} not found."}
        
    if task["status"] != "accepted":
        return {"error": f"Task must be 'accepted' to submit for review. Current status: {task['status']}"}
        
    task["status"] = "in_review"
    task["deliverable"] = deliverable
    
    _write_task(task)
    
    # Broadcast to the task's channel that it needs review
    from brynq.server import PLATFORM, SESSION_NAME
    msg = f"[REVIEW REQUEST] {task['title']} (id: {task['task_id']}) needs peer review. Deliverable: {deliverable}"
    _notify_channel(task["channel"], msg)
    
    return {"status": "in_review", "task_id": task_id}

def accept_review(task_id: str) -> dict:
    """An agent accepts the responsibility of reviewing the task."""
    task = _read_task(task_id)
    if not task:
        return {"error": f"Task {task_id} not found."}
        
    if task["status"] != "in_review":
        return {"error": f"Task must be 'in_review' to accept review. Current status: {task['status']}"}
        
    from brynq.server import PLATFORM, SESSION_NAME, SESSION_ID
    
    if task.get("assignee_session") == SESSION_ID:
        return {"error": "You cannot review your own task!"}
        
    task["reviewer_session"] = SESSION_ID
    task["reviewer_name"] = f"{SESSION_NAME}@{PLATFORM}"
    _write_task(task)
    
    _notify_channel(task["channel"], f"[REVIEW ACCEPTED] {task['title']} (id: {task['task_id']}) is being reviewed by {task['reviewer_name']}.")
    return {"status": "reviewing", "task_id": task_id}

def approve_task(task_id: str, feedback: str = "") -> dict:
    """Approve a task in review and mark it completed."""
    task = _read_task(task_id)
    if not task:
        return {"error": f"Task {task_id} not found."}
        
    if task["status"] != "in_review":
        return {"error": f"Task must be 'in_review' to approve. Current status: {task['status']}"}
        
    from brynq.server import PLATFORM, SESSION_NAME, SESSION_ID
    
    if task.get("reviewer_session") and task.get("reviewer_session") != SESSION_ID:
        return {"error": f"Task is being reviewed by {task['reviewer_name']}."}
        
    if task.get("assignee_session") == SESSION_ID:
        return {"error": "You cannot approve your own task!"}
        
    task["status"] = "completed"
    task["completed_at"] = datetime.datetime.now(datetime.timezone.utc).isoformat()
    task["result"] = f"APPROVED by {SESSION_NAME}@{PLATFORM}. Feedback: {feedback}"
    
    _write_task(task)
    _notify_channel(task["channel"], f"[TASK COMPLETED - APPROVED] {task['title']} (id: {task['task_id']})\nFeedback: {feedback}")
    return {"status": "completed", "task_id": task_id}

def reject_task(task_id: str, feedback: str) -> dict:
    """Reject a task in review and send it back to the assignee."""
    task = _read_task(task_id)
    if not task:
        return {"error": f"Task {task_id} not found."}
        
    if task["status"] != "in_review":
        return {"error": f"Task must be 'in_review' to reject. Current status: {task['status']}"}
        
    from brynq.server import PLATFORM, SESSION_NAME, SESSION_ID
    
    if task.get("reviewer_session") and task.get("reviewer_session") != SESSION_ID:
        return {"error": f"Task is being reviewed by {task['reviewer_name']}."}
        
    task["status"] = "accepted"  # Send back to active work
    _write_task(task)
    _notify_channel(task["channel"], f"[TASK REJECTED] {task['title']} (id: {task['task_id']}) was rejected by {SESSION_NAME}@{PLATFORM}. Reason: {feedback}")
    return {"status": "accepted", "task_id": task_id}
