from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Callable, Literal
import json

ROLE_DIR = Path("/root/projects/meeting/agents")


@dataclass
class AgentConfig:
    name: str
    role: str
    system_prompt: str
    input_schema: str = ""
    output_schema: str = ""


@dataclass
class ScratchpadEntry:
    speaker: str
    content: str
    timestamp: datetime = field(default_factory=datetime.now)


@dataclass
class MeetingState:
    topic: str
    context: str = ""
    scratchpad: list[ScratchpadEntry] = field(default_factory=list)
    current_round: int = 0
    max_rounds: int = 10
    phase: Literal["DIVERGE", "DEBATE", "CONVERGE", "DECIDE"] = "DIVERGE"


@dataclass
class PMDecision:
    analysis: str
    next_action: Literal["CALL_AGENT", "FINISH"]
    target_agent: str | None = None
    prompt_for_agent: str | None = None
    final_report: str | None = None
    phase: Literal["DIVERGE", "DEBATE", "CONVERGE", "DECIDE"] | None = None


@dataclass
class MeetingPlan:
    topic: str
    roles: list[str]
    context: str = ""
    max_rounds: int = 10
    human_in_the_loop: bool = True


@dataclass
class MeetingRunResult:
    report: str
    transcript: list[ScratchpadEntry]
    participants: list[str]
    started_at: datetime
    finished_at: datetime


@dataclass
class RoleAnalysisResult:
    role_name: str
    prompt: str
    response: str
    started_at: datetime
    finished_at: datetime


@dataclass
class WorkflowState:
    plan: MeetingPlan
    meeting: MeetingState
    participants: dict[str, AgentConfig]
    paused: bool = False
    pause_reason: str = ""


def load_role_config(role_name: str) -> AgentConfig:
    path = ROLE_DIR / f"{role_name}.json"
    data = json.loads(path.read_text(encoding="utf-8"))
    return AgentConfig(
        name=data["name"],
        role=data["role"],
        system_prompt=data["system_prompt"],
        input_schema=data.get("input_schema", ""),
        output_schema=data.get("output_schema", ""),
    )


def load_role_names() -> list[str]:
    return sorted(p.stem for p in ROLE_DIR.glob("*.json"))


def build_meeting_plan(topic: str, roles: list[str], context: str = "", max_rounds: int = 10) -> MeetingPlan:
    return MeetingPlan(topic=topic, roles=roles, context=context, max_rounds=max_rounds)


def format_scratchpad(entries: list[ScratchpadEntry]) -> str:
    if not entries:
        return "No discussion yet."
    return "\n".join(f"[{e.speaker}]: {e.content}" for e in entries)


def summarize_latest_turns(entries: list[ScratchpadEntry], limit: int = 3) -> str:
    if not entries:
        return "No prior turns."
    recent = entries[-limit:]
    return "\n".join(f"- {e.speaker}: {e.content}" for e in recent)


def extract_key_points(entries: list[ScratchpadEntry], limit: int = 5) -> str:
    if not entries:
        return "No key points yet."
    recent = entries[-limit:]
    return "\n".join(f"- {e.speaker}: {e.content[:240]}" for e in recent)


def infer_phase(state: MeetingState) -> Literal["DIVERGE", "DEBATE", "CONVERGE", "DECIDE"]:
    count = len(state.scratchpad)
    if count < 2:
        return "DIVERGE"
    if count < 4:
        return "DEBATE"
    if count < 6:
        return "CONVERGE"
    return "DECIDE"


def current_focus(entries: list[ScratchpadEntry]) -> str:
    if not entries:
        return "No prior claims yet."
    latest = entries[-1]
    return f"Latest claim to respond to: {latest.speaker} said: {latest.content[:400]}"


def build_pm_prompt(plan: MeetingPlan, state: MeetingState, participants: dict[str, AgentConfig]) -> str:
    state.phase = infer_phase(state)
    role_list = "\n".join(f"- {name}: {cfg.role}" for name, cfg in participants.items())
    scratchpad = format_scratchpad(state.scratchpad)
    latest = summarize_latest_turns(state.scratchpad, limit=3)
    key_points = extract_key_points(state.scratchpad, limit=5)
    focus = current_focus(state.scratchpad)
    return (
        f"## Topic\n\n{plan.topic}\n\n"
        f"## Background Context\n\n{plan.context or 'None'}\n\n"
        f"## Meeting Phase\n\n{state.phase}\n\n"
        f"## Participants\n\n{role_list}\n\n"
        f"## Current Whiteboard (Round {state.current_round})\n\n{scratchpad}\n\n"
        f"## Most Recent Turns\n\n{latest}\n\n"
        f"## Key Points So Far\n\n{key_points}\n\n"
        f"## Focus For Next Turn\n\n{focus}\n\n"
        "## PM Instructions\n\n"
        "1. If phase is DIVERGE, ask for distinct hypotheses or options.\n"
        "2. If phase is DEBATE, make the next role directly respond to the latest claim.\n"
        "3. If phase is CONVERGE, force synthesis: ask what survives and what gets dropped.\n"
        "4. If phase is DECIDE, finish with an explicit recommendation, tradeoffs, and next actions.\n"
        "5. Never choose a new speaker without tying them to a prior claim or unresolved question."
    )


def build_agent_prompt(topic: str, context: str, scratchpad: str, task: str) -> str:
    sections = [f"## Topic\n\n{topic}"]
    if context:
        sections.append(f"## Context\n\n{context}")
    sections.append(f"## Current Meeting Whiteboard\n\n{scratchpad}")
    sections.append(
        "## Response Rules\n\n"
        "- Respond directly to the latest disagreement or the specific claim named by the PM.\n"
        "- Explicitly say whether you are extending, opposing, or synthesizing the prior claim.\n"
        "- Do not restate the whole topic from scratch.\n"
        "- If you agree, say what changed your mind or what you are adding.\n"
        "- If you disagree, name the exact prior claim and why it fails.\n"
        "- End with a concrete next step, question, or synthesis."
    )
    sections.append(f"## Your Task\n\n{task}")
    return "\n\n".join(sections)


def build_single_role_prompt(role_name: str, topic: str, context: str = "") -> tuple[AgentConfig, str]:
    role = load_role_config(role_name)
    scratchpad = "No discussion yet."
    task = (
        "Evaluate the topic directly, but do it like a high-signal meeting participant. "
        "Start with your conclusion, then challenge the biggest hidden assumption, then propose the next move."
    )
    return role, build_agent_prompt(topic=topic, context=context, scratchpad=scratchpad, task=task)


def build_feedback_prompt(plan: MeetingPlan, state: MeetingState) -> str:
    return (
        f"## Topic\n\n{plan.topic}\n\n"
        f"## Current Round\n\n{state.current_round}\n\n"
        f"## Whiteboard\n\n{format_scratchpad(state.scratchpad)}\n\n"
        "## User Feedback\n\nWhat should change in the meeting?"
    )


def build_workflow_state(plan: MeetingPlan) -> WorkflowState:
    meeting = MeetingState(topic=plan.topic, context=plan.context, max_rounds=plan.max_rounds)
    participants = {name: load_role_config(name) for name in plan.roles}
    return WorkflowState(plan=plan, meeting=meeting, participants=participants)


def pause_workflow(state: WorkflowState, reason: str) -> WorkflowState:
    state.paused = True
    state.pause_reason = reason
    return state


def resume_workflow(state: WorkflowState) -> WorkflowState:
    state.paused = False
    state.pause_reason = ""
    return state


def run_meeting_native(
    plan: MeetingPlan,
    client,
    select_participants: Callable[[MeetingPlan], list[str]],
    route_pm: Callable[[MeetingPlan, MeetingState, dict[str, AgentConfig], object], PMDecision],
    run_role: Callable[[AgentConfig, str, object], str],
    interrupt_hook: Callable[..., None] | None = None,
) -> MeetingRunResult:
    started_at = datetime.now()
    state = MeetingState(topic=plan.topic, context=plan.context, max_rounds=plan.max_rounds)
    participants = {name: load_role_config(name) for name in select_participants(plan)}
    final_report = ""

    while state.current_round < state.max_rounds:
        state.current_round += 1
        decision = route_pm(plan, state, participants, client)
        if decision.phase:
            state.phase = decision.phase
        if decision.next_action == "FINISH":
            final_report = decision.final_report or "# Meeting Report\n\nNo report generated."
            break

        if decision.target_agent not in participants:
            raise ValueError(f"PM selected unavailable agent: {decision.target_agent}")

        scratchpad_text = format_scratchpad(state.scratchpad)
        agent_prompt = build_agent_prompt(
            topic=plan.topic,
            context=plan.context,
            scratchpad=scratchpad_text,
            task=decision.prompt_for_agent or "Respond to the latest claim and move the meeting forward.",
        )
        response = run_role(participants[decision.target_agent], agent_prompt, client)
        state.scratchpad.append(ScratchpadEntry(speaker=decision.target_agent, content=response))

        if interrupt_hook is not None:
            interrupt_hook(plan=plan, state=state, latest_agent=decision.target_agent, latest_response=response)

    if not final_report:
        final_report = "# Meeting Report\n\nMeeting ended after maximum rounds without PM conclusion."

    return MeetingRunResult(
        report=final_report,
        transcript=list(state.scratchpad),
        participants=list(participants.keys()),
        started_at=started_at,
        finished_at=datetime.now(),
    )


def run_single_role_analysis(role_name: str, topic: str, context: str, client, run_role: Callable[[AgentConfig, str, object], str]) -> RoleAnalysisResult:
    started_at = datetime.now()
    role, prompt = build_single_role_prompt(role_name=role_name, topic=topic, context=context)
    response = run_role(role, prompt, client)
    return RoleAnalysisResult(
        role_name=role_name,
        prompt=prompt,
        response=response,
        started_at=started_at,
        finished_at=datetime.now(),
    )
