"""Project info fragment tracker for Obsidian Bot mode info."""

import re
from datetime import datetime, timezone
from pathlib import Path
from typing import List, Optional, Tuple

from pydantic import BaseModel, Field

from health.utils.logging_config import setup_logger
from slack_bot.llm.gemini import GeminiLLM

logger = setup_logger(__name__)

# Match (From xxx) or (from xxx) — case-insensitive, captures the source name
_SOURCE_RE = re.compile(r'\((?:[Ff]rom|FROM)\s+(.+?)\)\s*$')

INFO_SYSTEM_PROMPT = """You are a concise project note-taker.
You help organize and comment on project information fragments.
Always respond in Chinese unless the input is entirely in English."""


class InfoEntry(BaseModel):
    """Single information fragment parsed from a weekly Markdown file."""

    timestamp: str
    tags: List[str] = Field(default_factory=list)
    source: str = ""
    raw_content: str
    comment: str = ""
    updates: List[str] = Field(default_factory=list)
    # Line position in the source file for update insertion
    source_line_start: int = 0
    source_line_end: int = 0


class InfoManager:
    """Manages per-project weekly info files in the Obsidian vault.

    Directory layout::

        vault/info/<project>/YYYY-Www.md
    """

    def __init__(self, vault_path: Path):
        self.vault_path = vault_path
        self.info_dir = vault_path / "info"
        self.llm = GeminiLLM(system_instruction=INFO_SYSTEM_PROMPT)

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

    def add_entry(self, project: str, content: str) -> str:
        """Add a new info entry to the current week file.

        Args:
            project: Project directory name.
            content: Raw user input (text with optional '(From xxx)' suffix).

        Returns:
            Slack confirmation message.
        """
        raw_text, source = self._extract_source(content)
        if not raw_text.strip():
            return "⚠️ 内容为空，未保存。"

        # Load recent entries for context
        week_file = self._get_week_file(project)
        existing = ""
        if week_file.exists():
            existing = week_file.read_text(encoding="utf-8")

        comment, tags = self._generate_comment_and_tags(raw_text, project, existing)

        now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M")
        tag_str = " ".join(tags) if tags else ""
        header = f"### {now} | {tag_str}".rstrip()

        lines = [
            header,
            "",
            *[f"> {line}" for line in raw_text.strip().split("\n")],
            "",
        ]
        if source:
            lines.append(f"来源: {source}")
            lines.append("")
        lines.append(f"**点评**: {comment}")
        lines.append("")
        lines.append("---")
        lines.append("")

        entry_text = "\n".join(lines)

        # Ensure directory and file exist
        week_file.parent.mkdir(parents=True, exist_ok=True)
        with open(week_file, "a", encoding="utf-8") as f:
            f.write(entry_text)

        tag_display = f" {tag_str}" if tag_str else ""
        source_display = f"\n📎 来源: {source}" if source else ""
        logger.info(f"InfoManager: added entry to {project}/{week_file.name}{tag_display}")
        return f"✅ 已保存到 *{project}*{tag_display}{source_display}\n\n> {comment}"

    def list_recent(self, project: str, count: int = 5) -> Tuple[str, List[InfoEntry]]:
        """List the most recent entries for a project.

        Args:
            project: Project directory name.
            count: Max entries to show.

        Returns:
            Tuple of (formatted message, entry list).
        """
        entries = self._load_recent_entries(project, count)
        if not entries:
            return f"📭 项目 *{project}* 暂无记录。", []

        lines = [f"📋 *{project}* 最近 {len(entries)} 条信息：\n"]
        for i, e in enumerate(entries, 1):
            tag_str = " ".join(e.tags) if e.tags else ""
            # Truncate raw content for display
            preview = e.raw_content[:60].replace("\n", " ")
            if len(e.raw_content) > 60:
                preview += "..."
            update_count = f" (+{len(e.updates)}更新)" if e.updates else ""
            lines.append(f"*{i}.* {e.timestamp} {tag_str}{update_count}\n   {preview}")
        lines.append("\n输入序号选择要更新的条目，或发送其他内容新增信息。")
        return "\n".join(lines), entries

    def update_entry(
        self, project: str, entries: List[InfoEntry], index: int, content: str
    ) -> str:
        """Append an update to an existing entry.

        Args:
            project: Project directory name.
            entries: Entry list from list_recent.
            index: 0-based index into entries.
            content: Update content from user.

        Returns:
            Slack confirmation message.
        """
        if index < 0 or index >= len(entries):
            return f"⚠️ 无效序号，请输入 1-{len(entries)} 之间的数字。"

        entry = entries[index]
        now = datetime.now(timezone.utc).strftime("%Y-%m-%d")
        update_line = f"**更新 {now}**: {content.strip()}"

        # Find the source file and insert before the closing ---
        week_files = self._get_week_files(project)
        for wf in week_files:
            file_lines = wf.read_text(encoding="utf-8").split("\n")
            # Match entry by timestamp header
            header_pattern = f"### {entry.timestamp}"
            for i, line in enumerate(file_lines):
                if line.startswith(header_pattern):
                    # Find the next --- separator
                    for j in range(i + 1, len(file_lines)):
                        if file_lines[j].strip() == "---":
                            # Insert update before ---
                            file_lines.insert(j, "")
                            file_lines.insert(j, update_line)
                            wf.write_text("\n".join(file_lines), encoding="utf-8")
                            logger.info(f"InfoManager: updated entry {entry.timestamp} in {project}")
                            return f"✅ 已更新 *{entry.timestamp}* 的记录。\n\n> {update_line}"
                    break

        return "⚠️ 未找到对应条目，可能文件已被修改。"

    def summarize(self, project: str, extra_paths: Optional[List[str]] = None) -> str:
        """Generate a project summary from all info entries.

        Args:
            project: Project directory name.
            extra_paths: Optional vault-relative paths to include.

        Returns:
            LLM-generated summary.
        """
        all_content = self._load_all_content(project)
        if not all_content.strip():
            return f"📭 项目 *{project}* 暂无记录，无法汇总。"

        extra_text = ""
        if extra_paths:
            for rel_path in extra_paths:
                fp = self.vault_path / rel_path.strip()
                if fp.exists():
                    text = fp.read_text(encoding="utf-8", errors="ignore")[:8000]
                    extra_text += f"\n\n=== 外部笔记: {rel_path} ===\n{text}"
                else:
                    extra_text += f"\n\n⚠️ 未找到: {rel_path}"

        prompt = f"""根据以下项目 "{project}" 的碎片信息，生成汇总报告。

要求：
1. 项目进展时间线（按时间排列关键事件，标注 [日期, 来源]）
2. 当前待解决问题
3. 关键决策和结论
4. 如有外部笔记，融合其内容进行分析

用中文回答，简洁直接。

=== 项目碎片信息 ===
{all_content}
{extra_text}
"""
        response, _ = self.llm.generate_response(prompt, [])
        logger.info(f"InfoManager: generated summary for {project}")
        return response

    def ask(self, project: str, question: str, extra_paths: Optional[List[str]] = None) -> str:
        """Answer a free-form question based on project info entries.

        Args:
            project: Project directory name.
            question: User's question.
            extra_paths: Optional vault-relative paths to include as context.

        Returns:
            LLM-generated answer.
        """
        all_content = self._load_all_content(project)
        if not all_content.strip():
            return f"📭 项目 *{project}* 暂无记录，无法回答。"

        extra_text = ""
        if extra_paths:
            for rel_path in extra_paths:
                fp = self.vault_path / rel_path.strip()
                if fp.exists():
                    text = fp.read_text(encoding="utf-8", errors="ignore")[:8000]
                    extra_text += f"\n\n=== 外部笔记: {rel_path} ===\n{text}"
                else:
                    extra_text += f"\n\n⚠️ 未找到: {rel_path}"

        prompt = f"""根据以下项目 "{project}" 的碎片信息，回答用户问题。

要求：
- 只基于提供的信息回答，没有相关信息就直说
- 每个论点必须标注出处：[日期, 来源]（如 [2026-03-17, 张三]），方便人工复核
- 如果条目没有来源字段，只标注日期
- 用中文回答，简洁直接

=== 项目碎片信息 ===
{all_content}
{extra_text}

=== 用户问题 ===
{question}
"""
        response, _ = self.llm.generate_response(prompt, [])
        logger.info(f"InfoManager: answered question for {project}")
        return response

    def search_entries(self, project: str, keyword: str) -> str:
        """Search entries by keyword across all week files.

        Args:
            project: Project directory name.
            keyword: Search term.

        Returns:
            Formatted search results.
        """
        week_files = self._get_week_files(project)
        if not week_files:
            return f"📭 项目 *{project}* 暂无记录。"

        matches: List[str] = []
        kw_lower = keyword.lower()
        for wf in week_files:
            entries = self._parse_entries(wf)
            for e in entries:
                full_text = f"{e.raw_content} {e.comment} {' '.join(e.updates)}"
                if kw_lower in full_text.lower():
                    tag_str = " ".join(e.tags) if e.tags else ""
                    preview = e.raw_content[:80].replace("\n", " ")
                    matches.append(f"• {e.timestamp} {tag_str}\n  {preview}")

        if not matches:
            return f"🔍 在 *{project}* 中未找到包含 \"{keyword}\" 的记录。"

        header = f"🔍 在 *{project}* 中找到 {len(matches)} 条匹配 \"{keyword}\"：\n"
        return header + "\n".join(matches[:10])

    def list_projects(self) -> str:
        """List all project directories.

        Returns:
            Formatted project list.
        """
        if not self.info_dir.exists():
            return "📭 暂无任何项目。使用 `project <名称>` 创建第一个项目。"

        projects = sorted(
            d.name for d in self.info_dir.iterdir() if d.is_dir()
        )
        if not projects:
            return "📭 暂无任何项目。使用 `project <名称>` 创建第一个项目。"

        lines = [f"📁 共 {len(projects)} 个项目：\n"]
        for p in projects:
            week_files = list((self.info_dir / p).glob("*.md"))
            entry_count = sum(
                len(self._parse_entries(wf)) for wf in week_files
            )
            lines.append(f"• *{p}* — {entry_count} 条记录，{len(week_files)} 个周文件")
        return "\n".join(lines)

    # ── Internal ──────────────────────────────────────────────────────

    def _get_week_file(self, project: str) -> Path:
        """Get the path for the current week's file, creating dir if needed."""
        now = datetime.now(timezone.utc)
        iso_year, iso_week, _ = now.isocalendar()
        filename = f"{iso_year}-W{iso_week:02d}.md"
        project_dir = self.info_dir / project
        project_dir.mkdir(parents=True, exist_ok=True)
        return project_dir / filename

    def _get_week_files(self, project: str) -> List[Path]:
        """Get all week files for a project, sorted newest first."""
        project_dir = self.info_dir / project
        if not project_dir.exists():
            return []
        files = sorted(project_dir.glob("*.md"), reverse=True)
        return files

    def _parse_entries(self, file_path: Path) -> List[InfoEntry]:
        """Parse a weekly Markdown file into structured entries."""
        if not file_path.exists():
            return []

        text = file_path.read_text(encoding="utf-8")
        entries: List[InfoEntry] = []

        # Split by ### headers
        blocks = re.split(r'^(### .+)$', text, flags=re.MULTILINE)

        # blocks = [preamble, header1, body1, header2, body2, ...]
        i = 1
        while i < len(blocks) - 1:
            header_line = blocks[i].strip()
            body = blocks[i + 1]

            # Parse header: ### 2026-03-17 14:30 | #ci #性能
            header_match = re.match(
                r'### (\d{4}-\d{2}-\d{2} \d{2}:\d{2})\s*(?:\|\s*(.*))?$',
                header_line,
            )
            if not header_match:
                i += 2
                continue

            timestamp = header_match.group(1)
            tag_str = header_match.group(2) or ""
            tags = [t.strip() for t in tag_str.split() if t.strip().startswith("#")]

            # Parse body
            raw_lines: List[str] = []
            comment = ""
            source = ""
            updates: List[str] = []

            for line in body.split("\n"):
                stripped = line.strip()
                if stripped.startswith("> "):
                    raw_lines.append(stripped[2:])
                elif stripped.startswith("来源: ") or stripped.startswith("来源:"):
                    source = stripped.split(":", 1)[1].strip()
                elif stripped.startswith("**点评**:") or stripped.startswith("**点评**: "):
                    comment = stripped.split(":", 1)[1].strip()
                elif stripped.startswith("**更新"):
                    updates.append(stripped)

            entries.append(InfoEntry(
                timestamp=timestamp,
                tags=tags,
                source=source,
                raw_content="\n".join(raw_lines),
                comment=comment,
                updates=updates,
            ))
            i += 2

        return entries

    def _load_recent_entries(self, project: str, count: int) -> List[InfoEntry]:
        """Load the most recent N entries across week files."""
        week_files = self._get_week_files(project)
        all_entries: List[InfoEntry] = []
        for wf in week_files:
            entries = self._parse_entries(wf)
            all_entries.extend(reversed(entries))  # newest first within file
            if len(all_entries) >= count:
                break
        return all_entries[:count]

    def _load_all_content(self, project: str) -> str:
        """Load all week files as raw text, oldest first."""
        week_files = self._get_week_files(project)
        parts: List[str] = []
        for wf in reversed(week_files):  # oldest first
            parts.append(f"=== {wf.stem} ===\n")
            parts.append(wf.read_text(encoding="utf-8"))
        return "\n".join(parts)

    def _extract_source(self, content: str) -> Tuple[str, str]:
        """Extract '(From xxx)' source tag from content.

        Returns:
            Tuple of (cleaned text, source name or empty string).
        """
        match = _SOURCE_RE.search(content)
        if match:
            source = match.group(1).strip()
            text = content[:match.start()].strip()
            return text, source
        return content, ""

    def _generate_comment_and_tags(
        self, content: str, project: str, context: str
    ) -> Tuple[str, List[str]]:
        """Use LLM to generate a one-line comment and 1-3 tags.

        Args:
            content: The raw information text.
            project: Current project name.
            context: Existing entries in the current week file (for tag consistency).

        Returns:
            Tuple of (comment string, list of tag strings like ["#ci", "#性能"]).
        """
        # Truncate context to avoid prompt bloat
        ctx_preview = context[-1500:] if len(context) > 1500 else context

        prompt = f"""根据以下信息，生成：
1. 一句话点评（不超过50字，直接说判断和建议，不要废话）
2. 1-3 个标签（用 # 前缀，简短，和已有标签保持一致性）

项目名: {project}
已有条目（参考标签风格）:
{ctx_preview}

新信息:
{content}

严格按以下 JSON 格式输出，不要输出其他内容:
{{"comment": "...", "tags": ["#tag1", "#tag2"]}}"""

        try:
            response, _ = self.llm.generate_response(prompt, [])
            # Parse JSON from response
            json_match = re.search(r'\{[^}]+\}', response, re.DOTALL)
            if json_match:
                import json
                data = json.loads(json_match.group(0))
                comment = data.get("comment", "")
                tags = data.get("tags", [])
                # Ensure tags have # prefix
                tags = [t if t.startswith("#") else f"#{t}" for t in tags]
                return comment, tags[:3]
        except Exception as e:
            logger.warning(f"LLM comment/tag generation failed: {e}")

        return "（自动点评生成失败）", []
