from __future__ import annotations

import logging
from pathlib import Path
from threading import Event, Thread
from time import sleep
from uuid import uuid4

from .agent import TaskPlanner
from .executor import LocalCommandExecutor
from .models import ChatEntry, CommandRecord, TaskNote, TaskState, TaskStatus, TaskSummary, utc_now
from .notifier import WorkerNotifier
from .state_store import StateStore

logger = logging.getLogger(__name__)


class WorkerController:
    def __init__(
        self,
        store: StateStore,
        executor: LocalCommandExecutor,
        planner: TaskPlanner,
        notifier: WorkerNotifier,
        poll_seconds: float = 2.0,
    ) -> None:
        self.store = store
        self.executor = executor
        self.planner = planner
        self.notifier = notifier
        self.poll_seconds = poll_seconds
        self.stop_event = Event()
        self.thread = Thread(target=self._run_loop, name="codexbot-worker", daemon=True)
        self.thread.start()

    def get_state(self) -> TaskState:
        return self.store.load()

    def get_task_state(self, task_ref: str) -> TaskState:
        resolved_task_id = self._resolve_task_ref(task_ref)
        return self.store.load(task_id=resolved_task_id)

    def list_task_summaries(self) -> list[TaskSummary]:
        return self.store.load_index().tasks

    def list_tasks(self) -> str:
        index = self.store.load_index()
        if not index.tasks:
            return "還沒有任務。"
        lines = [f"目前任務: {index.active_task_id}"]
        for task in index.tasks[:10]:
            marker = "*" if task.task_id == index.active_task_id else "-"
            lines.append(
                f"{marker} {task.task_id}: {task.title} [{task.status.value}] 待處理={task.pending_items} 已完成={task.completed_steps}"
            )
        return "\n".join(lines)

    def create_task(self, title: str) -> TaskState:
        task_id = uuid4().hex[:8]
        workdir = str(self.executor.workdir)
        state = self.store.create_task(task_id, title.strip() or f"Task {task_id}", workdir=workdir)
        state.summary = "已建立新任務。"
        state.touch(f"已建立並切換到任務 {task_id}。")
        self.store.save(state, task_id=task_id)
        return state

    def switch_task(self, task_id: str) -> TaskState:
        resolved_task_id = self._resolve_task_ref(task_id)
        state = self.store.load(task_id=resolved_task_id)
        self.store.set_active_task(resolved_task_id)
        state.touch(f"已切換到任務 {resolved_task_id}。")
        self.store.save(state, task_id=resolved_task_id)
        return state

    def continue_task(self, instruction: str | None = None) -> TaskState:
        state = self.store.load()
        state.status = TaskStatus.running
        if state.title == "目前沒有任務":
            state.title = "遠端任務"
            state.summary = "已收到遠端續作要求。"
        if state.workdir is None:
            state.workdir = str(self.executor.workdir)

        if instruction:
            state.notes.append(TaskNote(author="telegram", text=instruction))
            self._append_chat_entry(state, "telegram", instruction)
            state.touch(f"依照新備註繼續處理：{instruction}")
        else:
            self._append_chat_entry(state, "assistant", "已繼續目前任務。")
            state.touch("繼續目前任務。")

        self.store.save(state)
        return state

    def pause_task(self) -> TaskState:
        state = self.store.load()
        state.status = TaskStatus.paused
        state.touch("已由遠端暫停。")
        self.store.save(state)
        return state

    def add_note(self, text: str, author: str = "telegram") -> TaskState:
        state = self.store.load()
        state.notes.append(TaskNote(author=author, text=text))
        self._append_chat_entry(state, author, text)
        if state.workdir is None:
            state.workdir = str(self.executor.workdir)
        if state.status != TaskStatus.waiting_approval:
            state.status = TaskStatus.running
        state.touch(f"已記錄備註：{text}")
        self.store.save(state)
        return state

    def append_chat_entry(self, author: str, text: str) -> TaskState:
        state = self.store.load()
        self._append_chat_entry(state, author, text)
        state.touch()
        self.store.save(state)
        return state

    def run_command(self, command: str) -> TaskState:
        state = self.store.load()
        state.status = TaskStatus.running
        state.notes.append(TaskNote(author="telegram", text=f"cmd: {command.strip()}"))
        self._append_chat_entry(state, "telegram", f"cmd: {command.strip()}")
        if state.workdir is None:
            state.workdir = str(self.executor.workdir)
        state.touch(f"已加入命令佇列：{command.strip()}")
        self.store.save(state)
        return state

    def set_workdir(self, workdir: str) -> TaskState:
        path = Path(workdir).expanduser()
        path.mkdir(parents=True, exist_ok=True)
        state = self.store.load()
        state.workdir = str(path.resolve())
        state.touch(f"工作目錄已設為 {state.workdir}")
        self.store.save(state)
        return state

    def set_title(self, title: str) -> TaskState:
        state = self.store.load()
        state.title = title.strip() or state.title
        state.touch(f"任務標題已更新為：{state.title}")
        self.store.save(state)
        return state

    def set_agent_enabled(self, enabled: bool) -> TaskState:
        state = self.store.load()
        state.agent_enabled = enabled
        state.touch(f"智能代理已{'啟用' if enabled else '停用'}。")
        self.store.save(state)
        return state

    def reject_pending_command(self) -> TaskState:
        state = self.store.load()
        state.pending_approval_command = None
        state.pending_approval_reason = None
        state.pending_voice_command = None
        state.approved_command = None
        state.requires_approval = False
        if state.status == TaskStatus.waiting_approval:
            state.status = TaskStatus.paused
        state.touch("已拒絕待審批命令。")
        self.store.save(state)
        return state

    def stage_voice_command(self, command: str) -> TaskState:
        state = self.store.load()
        state.pending_voice_command = command.strip()
        state.touch(f"語音命令等待確認：{state.pending_voice_command}")
        self.store.save(state)
        return state

    def confirm_voice_command(self) -> TaskState:
        state = self.store.load()
        command = (state.pending_voice_command or "").strip()
        if not command:
            state.touch("目前沒有待確認的語音命令。")
            self.store.save(state)
            return state
        state.pending_voice_command = None
        self.store.save(state)
        return self.run_command(command)

    def cancel_voice_command(self) -> TaskState:
        state = self.store.load()
        state.pending_voice_command = None
        state.touch("已取消待確認的語音命令。")
        self.store.save(state)
        return state

    def reset_state(self) -> TaskState:
        state = TaskState(workdir=str(self.executor.workdir))
        state.touch("狀態已重設。")
        self.store.save(state)
        return state

    def format_recent_notes(self, limit: int = 5) -> str:
        state = self.store.load()
        notes = state.notes[-limit:]
        if not notes:
            return "還沒有備註。"
        lines = []
        for note in notes:
            status = "已處理" if note.processed_at else "待處理"
            lines.append(f"[{status}] {note.author}: {note.text}")
            if note.result_summary:
                lines.append(f"  -> {note.result_summary}")
        return "\n".join(lines)

    def format_command_history(self, limit: int = 5) -> str:
        state = self.store.load()
        history = state.command_history[-limit:]
        if not history:
            return "還沒有命令紀錄。"
        lines = []
        for record in history:
            lines.append(f"[退出碼 {record.exit_code}] {record.command}")
            lines.append(record.output_preview)
        return "\n".join(lines)

    def format_queue(self, limit: int = 10) -> str:
        state = self.store.load()
        pending = [note for note in state.notes if note.processed_at is None]
        if not pending:
            return "佇列目前是空的。"
        lines = [f"待處理項目: {len(pending)}"]
        for note in pending[:limit]:
            lines.append(f"- {note.author}: {note.text}")
        if len(pending) > limit:
            lines.append(f"... 另外還有 {len(pending) - limit} 項")
        return "\n".join(lines)

    def pending_count(self) -> int:
        state = self.store.load()
        return sum(1 for note in state.notes if note.processed_at is None)

    def progress_summary(self) -> str:
        state = self.store.load()
        pending = sum(1 for note in state.notes if note.processed_at is None)
        return (
            f"已完成步數: {state.completed_steps}\n"
            f"待處理項目: {pending}\n"
            f"最近規劃摘要: {state.planner_last_summary or '-'}\n"
            f"最近命令: {state.last_command or '-'}"
        )

    def brief_status_line(self) -> str:
        state = self.store.load()
        return (
            f"狀態={state.status.value} | 待處理={self.pending_count()} | "
            f"已完成={state.completed_steps} | 最近命令={state.last_command or '-'}"
        )

    def approve(self) -> TaskState:
        state = self.store.load()
        state.requires_approval = False
        if state.pending_approval_command:
            state.approved_command = state.pending_approval_command
        state.pending_approval_reason = None
        if state.status == TaskStatus.waiting_approval:
            state.status = TaskStatus.running
        state.touch("已收到批准，工作器會重新嘗試待執行命令。")
        self.store.save(state)
        self._notify(
            f"[{state.task_id}] 已批准待执行命令\n"
            f"{state.pending_approval_command or ''}\n"
            f"{self.brief_status_line()}".strip()
        )
        return state

    def shutdown(self) -> None:
        self.stop_event.set()
        self.thread.join(timeout=max(self.poll_seconds * 2, 1.0))

    def _run_loop(self) -> None:
        while not self.stop_event.is_set():
            try:
                self._process_once()
            except Exception:
                logger.exception("Worker loop iteration failed")
            finally:
                sleep(self.poll_seconds)

    def _process_once(self) -> None:
        state = self.store.load()
        if state.status not in {TaskStatus.running, TaskStatus.waiting_approval, TaskStatus.blocked}:
            return

        for note in state.notes:
            if note.processed_at is not None:
                continue
            if state.status != TaskStatus.running and note.text.strip().lower().startswith("cmd:"):
                continue
            self._process_note(state, note)
            self.store.save(state)
            return

    def _process_note(self, state: TaskState, note: TaskNote) -> None:
        text = note.text.strip()
        if not text.lower().startswith("cmd:"):
            self._process_natural_language_note(state, note, text)
            return

        raw_command = text[4:].strip()
        approved = state.approved_command == raw_command
        decision = self.executor.prepare(raw_command, approved=approved)

        if not decision.should_execute:
            if decision.needs_approval:
                state.status = TaskStatus.waiting_approval
                state.requires_approval = True
                state.pending_approval_command = decision.display_command
                state.pending_approval_reason = decision.reason
                self._append_chat_entry(
                    state,
                    "assistant",
                    f"這條命令需要審批：{decision.display_command}\n原因：{decision.reason}",
                )
                state.touch(decision.reason)
                self._notify(
                    f"[{state.task_id}] 需要审批\n"
                    f"任务: {state.title}\n"
                    f"命令: {decision.display_command}\n"
                    f"原因: {decision.reason}\n"
                    f"{self.brief_status_line()}"
                )
                return

            note.processed_at = utc_now()
            note.result_summary = decision.reason
            self._append_chat_entry(state, "assistant", decision.reason)
            state.completed_steps += 1
            state.touch(decision.reason)
            return

        result = self.executor.execute(
            decision.argv,
            decision.display_command,
            workdir=Path(state.workdir) if state.workdir else None,
        )
        note.processed_at = utc_now()
        note.result_summary = f"退出碼={result.exit_code}"
        state.last_command = result.display_command
        state.last_command_output = result.output
        state.pending_approval_command = None
        state.pending_approval_reason = None
        state.approved_command = None
        state.requires_approval = False
        state.command_history.append(
            CommandRecord(
                command=result.display_command,
                exit_code=result.exit_code,
                output_preview=result.output,
            )
        )
        state.command_history = state.command_history[-20:]
        state.completed_steps += 1
        state.summary = f"上一條命令已執行完成，退出碼為 {result.exit_code}。"
        self._append_chat_entry(
            state,
            "assistant",
            f"已執行：{result.display_command}\n退出碼：{result.exit_code}\n{result.output}",
        )
        state.touch(f"已執行：{result.display_command}（退出碼 {result.exit_code}）")
        self._notify(
            f"[{state.task_id}] 命令执行完成\n"
            f"任务: {state.title}\n"
            f"命令: {result.display_command}\n"
            f"退出码: {result.exit_code}\n"
            f"{self.brief_status_line()}"
        )

    def _process_natural_language_note(self, state: TaskState, note: TaskNote, text: str) -> None:
        clean_title = self._project_title(state)
        if clean_title != state.title and self._looks_like_planner_title(state.title):
            state.title = clean_title

        direct_reply = self._build_direct_conversation_reply(state, text)
        if direct_reply is not None:
            note.processed_at = utc_now()
            note.result_summary = direct_reply
            self._append_chat_entry(state, "assistant", direct_reply)
            state.completed_steps += 1
            state.summary = direct_reply
            state.touch(direct_reply)
            return

        if state.status in {TaskStatus.waiting_approval, TaskStatus.blocked}:
            note.processed_at = utc_now()
            note.result_summary = self._build_state_reply(state)
            self._append_chat_entry(state, "assistant", note.result_summary)
            state.completed_steps += 1
            state.summary = note.result_summary
            state.touch(note.result_summary)
            return

        if not state.agent_enabled:
            note.processed_at = utc_now()
            note.result_summary = "僅記錄備註，智能代理目前停用。"
            self._append_chat_entry(state, "assistant", note.result_summary)
            state.completed_steps += 1
            state.touch("已記錄備註，智能代理目前停用。")
            return

        try:
            decision = self.planner.plan(state, text)
        except Exception as exc:
            logger.exception("Planner failed")
            note.processed_at = utc_now()
            note.result_summary = f"規劃失敗：{exc}"
            self._append_chat_entry(state, "assistant", note.result_summary)
            state.status = TaskStatus.blocked
            state.touch("規劃失敗，請檢查日誌、補充資訊，或暫時停用智能代理。")
            self._notify(
                f"[{state.task_id}] 规划失败\n"
                f"任务: {state.title}\n"
                f"错误: {exc}\n"
                f"{self.brief_status_line()}"
            )
            return
        note.processed_at = utc_now()
        if decision is None:
            note.result_summary = "僅記錄備註，目前無法使用規劃器。"
            self._append_chat_entry(state, "assistant", note.result_summary)
            state.completed_steps += 1
            state.touch("已記錄備註，目前無法使用規劃器。")
            return

        if decision.title and (
            state.title in {"目前沒有任務", "遠端任務", "No active task"}
            or state.title.startswith("Task ")
        ):
            state.title = decision.title
        state.planner_last_summary = decision.summary
        note.result_summary = decision.summary

        if decision.command:
            state.notes.append(TaskNote(author="planner", text=f"cmd: {decision.command}"))
            self._append_chat_entry(
                state,
                "assistant",
                f"{decision.summary}\n下一步命令：{decision.command}",
            )
            state.completed_steps += 1
            state.touch(f"規劃器已加入下一步命令：{decision.command}")
            self._notify(
                f"[{state.task_id}] 已生成下一步\n"
                f"任务: {state.title}\n"
                f"摘要: {decision.summary}\n"
                f"命令: {decision.command}\n"
                f"{self.brief_status_line()}"
            )
            return

        state.completed_steps += 1
        self._append_chat_entry(state, "assistant", decision.summary)
        state.touch(decision.summary)
        self._notify(
            f"[{state.task_id}] 规划更新\n"
            f"任务: {state.title}\n"
            f"{decision.summary}\n"
            f"{self.brief_status_line()}"
        )

    def _notify(self, text: str) -> None:
        try:
            self.notifier.notify(text)
        except Exception:
            logger.exception("Notifier failed")

    def _build_direct_conversation_reply(self, state: TaskState, text: str) -> str | None:
        normalized = self._canonicalize_text(text)
        lowered = normalized.casefold()

        if lowered in {
            "最近歷史",
            "查看最近歷史",
            "看看最近歷史",
            "查看歷史",
            "看看歷史",
        }:
            return self._build_recent_history_reply(state)

        if lowered in {
            "最近輸出",
            "查看最近輸出",
            "看看最近輸出",
            "查看輸出",
            "看看輸出",
        }:
            return state.last_command_output or "目前還沒有最近輸出。"

        if lowered in {
            "查看任務狀態",
            "看看任務狀態",
            "查看狀態",
            "看看狀態",
            "現在項目進行到什麼狀態了？",
            "現在項目進行到什麼狀態了",
            "現在項目情況怎麼樣？",
            "現在項目情況怎麼樣",
            "目前進度如何？",
            "目前進度如何",
            "現在進度如何？",
            "現在進度如何",
            "下一步做什麼？",
            "下一步做什麼",
        }:
            return self._build_state_reply(state)

        if lowered in {
            "現在是什麼項目？",
            "現在是什麼項目",
            "目前處於什麼項目？",
            "目前處於什麼項目",
            "目前是什麼項目？",
            "目前是什麼項目",
        }:
            return f"目前正在處理的專案是 {state.title}。"

        if lowered in {
            "查看進度",
            "看看進度",
            "進度",
        }:
            pending = sum(1 for item in state.notes if item.processed_at is None)
            return (
                f"目前專案是 {state.title}。\n"
                f"狀態：{self._localize_status(state.status)}\n"
                f"已完成步數：{state.completed_steps}\n"
                f"待處理項目：{pending}\n"
                f"最近更新：{state.latest_update}"
            )

        return None

    def _build_recent_history_reply(self, state: TaskState) -> str:
        history = state.command_history[-3:]
        if not history:
            return "還沒有命令紀錄。"
        lines = []
        for record in history:
            lines.append(f"命令：{record.command}")
            lines.append(f"退出碼：{record.exit_code}")
            if record.output_preview:
                lines.append(record.output_preview)
        return "\n".join(lines)

    def _build_state_reply(self, state: TaskState) -> str:
        pending = sum(1 for item in state.notes if item.processed_at is None)
        title = self._project_title(state)
        latest_update = self._best_status_summary(state)
        parts = [
            f"目前在處理 {title}。",
            f"狀態是{self._localize_status(state.status)}。",
        ]
        if state.pending_approval_command:
            parts.append(f"現在卡在一條待審批命令：{state.pending_approval_command}。")
        elif latest_update:
            parts.append(f"最近進展是：{latest_update}")
        if state.workdir:
            parts.append(f"工作目錄是 {state.workdir}。")
        parts.append(f"目前還有 {pending} 項待處理，已完成 {state.completed_steps} 步。")
        if state.last_command:
            parts.append(f"最近命令是 {state.last_command}。")
        return "\n".join(parts)

    def _canonicalize_text(self, text: str) -> str:
        normalized = text.strip()
        replacements = {
            "任务": "任務",
            "状态": "狀態",
            "进度": "進度",
            "历史": "歷史",
            "输出": "輸出",
            "项目": "專案",
            "现在": "現在",
            "查看": "查看",
            "看看": "看看",
        }
        for source, target in replacements.items():
            normalized = normalized.replace(source, target)
        return normalized

    def _project_title(self, state: TaskState) -> str:
        if state.workdir:
            basename = Path(state.workdir).name.strip()
            if basename and self._looks_like_planner_title(state.title):
                return basename
        return state.title

    def _best_status_summary(self, state: TaskState) -> str:
        candidates = [
            state.planner_last_summary,
            state.summary,
            state.latest_update,
        ]
        for candidate in candidates:
            if not candidate:
                continue
            normalized = candidate.strip()
            if not normalized:
                continue
            if normalized.startswith("已記錄備註："):
                continue
            if normalized.startswith("目前專案："):
                continue
            if "\n" in normalized:
                continue
            if self._looks_like_planner_title(normalized):
                continue
            return normalized
        if state.pending_approval_command:
            return "有一條命令正在等待審批。"
        return "目前沒有新的執行結果。"

    def _looks_like_planner_title(self, title: str | None) -> bool:
        if not title:
            return False
        normalized = title.strip()
        if not normalized:
            return False
        lowered = normalized.casefold()
        if "received image" in lowered or "attachment to project" in lowered or "copy new received" in lowered:
            return True
        return normalized.isascii() and len(normalized) >= 20

    def _localize_status(self, status: TaskStatus) -> str:
        mapping = {
            TaskStatus.idle: "閒置",
            TaskStatus.running: "執行中",
            TaskStatus.paused: "已暫停",
            TaskStatus.waiting_approval: "等待審批",
            TaskStatus.blocked: "已阻塞",
        }
        return mapping.get(status, status.value)

    def _append_chat_entry(self, state: TaskState, author: str, text: str) -> None:
        state.chat_log.append(ChatEntry(author=author, text=text))
        state.chat_log = state.chat_log[-120:]

    def _resolve_task_ref(self, task_ref: str) -> str:
        requested = task_ref.strip()
        try:
            self.store.load(task_id=requested)
            return requested
        except FileNotFoundError:
            index = self.store.load_index()
            lowered = requested.casefold()
            match = next((task for task in index.tasks if task.title.casefold() == lowered), None)
            if match is None:
                match = next((task for task in index.tasks if lowered in task.title.casefold()), None)
            if match is None:
                raise
            return match.task_id
