"""
Phase 73: Automated Conflict Resolution

When two or more agents produce competing changes to the same file, this
module registers the conflict, collects resolution proposals, lets agents
vote, and resolves by majority — or escalates to human review.

Storage:
  state/broker/conflicts/{conflict_id}.json
  state/broker/conflicts/history.jsonl
"""

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

from .server import BROKER_DIR, _write_json_sync, _read_jsonl_sync, _append_jsonl_sync

CONFLICTS_DIR = BROKER_DIR / "conflicts"


def _ensure_dir() -> None:
    CONFLICTS_DIR.mkdir(parents=True, exist_ok=True)


def _conflict_path(conflict_id: str) -> Path:
    return CONFLICTS_DIR / f"{conflict_id}.json"


def _history_path() -> Path:
    return CONFLICTS_DIR / "history.jsonl"


def _load(conflict_id: str) -> Optional[dict]:
    path = _conflict_path(conflict_id)
    if not path.exists():
        return None
    try:
        return json.loads(path.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return None


def _save(conflict: dict) -> None:
    _ensure_dir()
    _write_json_sync(_conflict_path(conflict["conflict_id"]), conflict)


def _log_event(event: dict) -> None:
    _ensure_dir()
    _append_jsonl_sync(_history_path(), event)


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


def report_conflict(
    file_path: str,
    agent_a: str,
    agent_b: str,
    description: str,
) -> dict:
    """Register a new conflict between two agents on a file."""
    _ensure_dir()
    conflict_id = uuid.uuid4().hex[:12]
    now = datetime.now(timezone.utc).isoformat()

    conflict = {
        "conflict_id": conflict_id,
        "file_path": file_path,
        "agent_a": agent_a,
        "agent_b": agent_b,
        "description": description,
        "status": "open",
        "proposals": [],
        "votes": {},
        "created_at": now,
        "resolved_at": None,
        "resolution": None,
        "escalated_at": None,
        "escalation_reason": None,
    }
    _save(conflict)
    _log_event({"event": "reported", "conflict_id": conflict_id, "timestamp": now})
    return conflict


def get_conflicts(status: Optional[str] = None) -> list[dict]:
    """List conflicts, optionally filtered by status (open/resolved/escalated)."""
    _ensure_dir()
    results: list[dict] = []
    for f in CONFLICTS_DIR.glob("*.json"):
        c = _load(f.stem)
        if c and (status is None or c.get("status") == status):
            results.append(c)
    results.sort(key=lambda c: c.get("created_at", ""), reverse=True)
    return results


def propose_resolution(
    conflict_id: str,
    agent_id: str,
    proposal: str,
) -> dict:
    """An agent proposes a resolution for a conflict."""
    conflict = _load(conflict_id)
    if conflict is None:
        return {"error": "Conflict not found"}
    if conflict["status"] != "open":
        return {"error": f"Conflict is {conflict['status']}, not open"}

    now = datetime.now(timezone.utc).isoformat()
    entry = {
        "index": len(conflict["proposals"]),
        "agent_id": agent_id,
        "proposal": proposal,
        "timestamp": now,
        "vote_count": 0,
    }
    conflict["proposals"].append(entry)
    _save(conflict)
    _log_event({
        "event": "proposal",
        "conflict_id": conflict_id,
        "agent_id": agent_id,
        "proposal_index": entry["index"],
        "timestamp": now,
    })
    return entry


def vote_on_resolution(
    conflict_id: str,
    voter_id: str,
    proposal_index: int,
) -> dict:
    """Cast a vote for a specific proposal.  One vote per agent."""
    conflict = _load(conflict_id)
    if conflict is None:
        return {"error": "Conflict not found"}
    if conflict["status"] != "open":
        return {"error": f"Conflict is {conflict['status']}, not open"}
    if proposal_index < 0 or proposal_index >= len(conflict["proposals"]):
        return {"error": "Invalid proposal index"}

    # Remove previous vote from tally if agent already voted
    prev = conflict["votes"].get(voter_id)
    if prev is not None:
        conflict["proposals"][prev]["vote_count"] = max(
            0, conflict["proposals"][prev]["vote_count"] - 1
        )

    conflict["votes"][voter_id] = proposal_index
    conflict["proposals"][proposal_index]["vote_count"] += 1
    _save(conflict)

    now = datetime.now(timezone.utc).isoformat()
    _log_event({
        "event": "vote",
        "conflict_id": conflict_id,
        "voter_id": voter_id,
        "proposal_index": proposal_index,
        "timestamp": now,
    })
    return {"voter_id": voter_id, "voted_for": proposal_index}


def resolve_conflict(conflict_id: str) -> dict:
    """Auto-resolve based on votes.  Majority wins; ties keep it open."""
    conflict = _load(conflict_id)
    if conflict is None:
        return {"error": "Conflict not found"}
    if conflict["status"] != "open":
        return {"error": f"Conflict is already {conflict['status']}"}
    if not conflict["proposals"]:
        return {"error": "No proposals to resolve"}

    # Find winning proposal
    best = max(conflict["proposals"], key=lambda p: p["vote_count"])
    # Check for tie
    top_votes = best["vote_count"]
    if top_votes == 0:
        return {"error": "No votes cast yet"}
    tied = [p for p in conflict["proposals"] if p["vote_count"] == top_votes]
    if len(tied) > 1:
        return {"error": "Tie — cannot auto-resolve", "tied_proposals": [t["index"] for t in tied]}

    now = datetime.now(timezone.utc).isoformat()
    conflict["status"] = "resolved"
    conflict["resolved_at"] = now
    conflict["resolution"] = {
        "winning_proposal": best["index"],
        "agent_id": best["agent_id"],
        "proposal": best["proposal"],
        "votes": best["vote_count"],
    }
    _save(conflict)
    _log_event({
        "event": "resolved",
        "conflict_id": conflict_id,
        "winning_proposal": best["index"],
        "timestamp": now,
    })
    return conflict


def escalate_conflict(conflict_id: str, reason: str = "") -> dict:
    """Send a conflict to human review."""
    conflict = _load(conflict_id)
    if conflict is None:
        return {"error": "Conflict not found"}
    if conflict["status"] not in ("open",):
        return {"error": f"Conflict is {conflict['status']}, cannot escalate"}

    now = datetime.now(timezone.utc).isoformat()
    conflict["status"] = "escalated"
    conflict["escalated_at"] = now
    conflict["escalation_reason"] = reason
    _save(conflict)
    _log_event({
        "event": "escalated",
        "conflict_id": conflict_id,
        "reason": reason,
        "timestamp": now,
    })
    return conflict


def get_conflict_stats() -> dict:
    """Aggregate statistics: total, resolved, escalated, avg resolution time."""
    _ensure_dir()
    total = 0
    resolved = 0
    escalated = 0
    open_count = 0
    resolution_times: list[float] = []

    for f in CONFLICTS_DIR.glob("*.json"):
        c = _load(f.stem)
        if c is None:
            continue
        total += 1
        st = c.get("status")
        if st == "resolved":
            resolved += 1
            if c.get("created_at") and c.get("resolved_at"):
                t0 = datetime.fromisoformat(c["created_at"])
                t1 = datetime.fromisoformat(c["resolved_at"])
                resolution_times.append((t1 - t0).total_seconds())
        elif st == "escalated":
            escalated += 1
        elif st == "open":
            open_count += 1

    avg_resolution_sec = (
        round(sum(resolution_times) / len(resolution_times), 1)
        if resolution_times
        else 0.0
    )
    return {
        "total": total,
        "open": open_count,
        "resolved": resolved,
        "escalated": escalated,
        "avg_resolution_seconds": avg_resolution_sec,
    }
