#!/usr/bin/env python3
"""
Life Index - Write Journal Tool - Core
核心协调模块
"""

import re
from typing import Any, Dict

from ..lib.config import JOURNALS_DIR, FILE_LOCK_TIMEOUT_DEFAULT, get_default_location
from ..lib.file_lock import FileLock, LockTimeoutError, get_journals_lock_path
from ..lib.errors import ErrorCode, create_error_response
from ..lib.timing import Timer
from ..lib.logger import get_logger

# 初始化日志器
logger = get_logger(__name__)

# 导入子模块
from .utils import get_year_month, generate_filename, get_next_sequence
from ..lib.frontmatter import format_journal_content as format_content
from .attachments import extract_file_paths_from_content, process_attachments
from .weather import query_weather_for_location, normalize_location
from .index_updater import (
    update_topic_index,
    update_project_index,
    update_tag_indices,
    update_monthly_abstract,
    update_vector_index,
)


def extract_explicit_metadata_from_content(content: str) -> Dict[str, str]:
    """从正文中提取明确声明的元数据（仅保守支持显式标签格式）"""
    extracted: Dict[str, str] = {}
    if not content:
        return extracted

    patterns = {
        "location": re.compile(
            r"^\s*(?:地点|位置|location)\s*[:：]\s*(.+?)\s*$", re.IGNORECASE
        ),
        "weather": re.compile(
            r"^\s*(?:天气|weather)\s*[:：]\s*(.+?)\s*$", re.IGNORECASE
        ),
    }

    for line in content.splitlines():
        for field, pattern in patterns.items():
            if field in extracted:
                continue
            match = pattern.match(line)
            if match:
                extracted[field] = match.group(1).strip()

    return extracted


def write_journal(data: Dict[str, Any], dry_run: bool = False) -> Dict[str, Any]:
    """
    写入日志的主函数
    自动处理默认值和天气查询

    Returns:
        {
            "success": bool,
            "journal_path": str,
            "updated_indices": [str],
            "index_status": str,
            "side_effects_status": str,
            "attachments_processed": [dict],
            "location_used": str,
            "weather_used": str,
            "weather_auto_filled": bool,
            "needs_confirmation": bool,
            "confirmation_message": str,
            "metrics": {"total_ms": float, "weather_query_ms": float, ...},
            "error": str (optional)
        }
    """
    # 性能计时器
    timer = Timer().start()

    result: Dict[str, Any] = {
        "success": False,
        "journal_path": None,
        "updated_indices": [],
        "index_status": "not_started",
        "side_effects_status": "not_started",
        "attachments_processed": [],
        "location_used": "",
        "location_auto_filled": False,
        "weather_used": "",
        "weather_auto_filled": False,
        "needs_confirmation": False,
        "confirmation_message": "",
        "error": None,
        "metrics": {},
    }

    try:
        # 验证必需字段
        date_str = data.get("date")
        if not date_str:
            logger.error("缺少必需字段：date")
            raise ValueError("缺少必需字段：date")

        logger.info(f"开始写入日志：date={date_str}, title={data.get('title', 'N/A')}")

        content = data.get("content", "")
        explicit_metadata = extract_explicit_metadata_from_content(content)

        # ===== 第一层：用户提及为准 =====
        # 如果正文里明确写了地点和天气，优先使用正文中的信息

        # ===== 第二层：自动填充 =====
        # 处理地点：如果未提供，使用默认值
        location = explicit_metadata.get("location") or data.get("location", "").strip()
        if not location:
            location = get_default_location()
            result["location_auto_filled"] = True
            result["location_used"] = location
            # 天气查询使用实际默认地点
            location_for_weather = normalize_location(location)
        else:
            # 规范化地点（处理城市级别输入）
            location_for_weather = normalize_location(location)
            result["location_used"] = location
        data["location"] = location

        # 处理天气：如果未提供，自动查询
        weather = explicit_metadata.get("weather") or data.get("weather", "").strip()
        if not weather:
            # 尝试获取天气（使用英文格式的地点）
            logger.debug(f"查询天气：location={location_for_weather}")
            with timer.measure("weather_query"):
                queried_weather = query_weather_for_location(
                    location_for_weather, date_str
                )
            if queried_weather:
                weather = queried_weather
                result["weather_used"] = weather
                result["weather_auto_filled"] = True
                logger.info(f"天气查询成功：{weather}")
            else:
                weather = ""
                result["weather_used"] = ""
                logger.warning("天气查询失败，使用空值")
        else:
            result["weather_used"] = weather
            logger.debug(f"使用用户提供的天气：{weather}")

        data["weather"] = weather

        # ===== 文件锁保护 =====
        # 使用文件锁保护序列号生成和写入操作，防止并发冲突
        logger.debug("获取文件锁...")
        with timer.measure("lock_acquire"):
            lock = FileLock(get_journals_lock_path(), timeout=FILE_LOCK_TIMEOUT_DEFAULT)

        try:
            with lock:
                # ===== 原子写入（带重试）=====
                # 处理并发写入时的序列号冲突（现在有锁保护，主要用于健壮性）
                max_retries = 3
                year = 0
                month = 0
                journal_path = None
                month_dir = None

                with timer.measure("sequence_gen"):
                    for retry in range(max_retries):
                        # 获取年月和序列号
                        year, month = get_year_month(date_str)
                        sequence = get_next_sequence(date_str)

                        # 构建路径
                        month_dir = JOURNALS_DIR / str(year) / f"{month:02d}"
                        filename = generate_filename(date_str, sequence)
                        journal_path = month_dir / filename

                        logger.debug(f"尝试生成文件名：{filename}, retry={retry}")

                        # 如果文件已存在且不是最后一次重试，重新获取序列号
                        if journal_path.exists() and retry < max_retries - 1:
                            logger.debug(f"文件已存在，准备重试：{journal_path}")
                            continue  # 重试
                        break  # 文件不存在，或最后一次重试直接使用

                # 类型安全断言（循环必定至少执行一次）
                assert journal_path is not None
                assert month_dir is not None

                # 从内容中自动检测文件路径
                content = data.get("content", "")
                auto_detected_paths = extract_file_paths_from_content(content)
                logger.debug(f"从内容中检测到 {len(auto_detected_paths)} 个附件路径")

                # 处理附件（显式附件 + 自动检测附件）
                attachments = data.get("attachments", [])
                with timer.measure("attachments"):
                    processed_attachments = process_attachments(
                        attachments, date_str, dry_run, auto_detected_paths
                    )
                result["attachments_processed"] = processed_attachments
                if processed_attachments:
                    logger.info(f"处理了 {len(processed_attachments)} 个附件")

                # 更新数据中的附件信息（用于生成内容）
                data["attachments"] = processed_attachments

                # 生成内容（format_journal_content 已包含 frontmatter + body）
                full_content = format_content(data)

                if dry_run:
                    result["journal_path"] = str(journal_path)
                    result["content_preview"] = full_content[:500]
                    result["success"] = True
                    timer.stop()
                    result["metrics"] = timer.to_dict()
                    return result

                # ===== 事务性写入 =====
                # 1. 准备所有索引更新所需数据
                topic = data.get("topic")
                project = data.get("project")
                tags = data.get("tags", [])

                # 2. 使用临时文件进行原子写入
                month_dir.mkdir(parents=True, exist_ok=True)
                temp_path = journal_path.with_suffix(".tmp")
                try:
                    logger.info(f"写入日志文件：{journal_path}")
                    with timer.measure("file_write"):
                        with open(temp_path, "w", encoding="utf-8") as f:
                            f.write(full_content)

                    # 3. 更新月度摘要
                    with timer.measure("abstract_update"):
                        abstract_result = None
                        abstract_error = None
                        abstract_success = False
                        try:
                            abstract_result = update_monthly_abstract(
                                year, month, dry_run
                            )
                            abstract_success = True
                        except (OSError, IOError, RuntimeError) as e:
                            abstract_error = str(e)

                    # 4. 更新索引
                    updated_indices = []
                    vector_index_error = None
                    with timer.measure("index_update"):
                        try:
                            if topic:
                                indices = update_topic_index(topic, journal_path, data)
                                updated_indices.extend([str(i) for i in indices])

                            if project:
                                idx = update_project_index(project, journal_path, data)
                                if idx:
                                    updated_indices.append(str(idx))

                            if tags:
                                indices = update_tag_indices(tags, journal_path, data)
                                updated_indices.extend([str(i) for i in indices])

                            # 6. 更新向量索引（Write-Through）
                            try:
                                vector_updated = update_vector_index(journal_path, data)
                                if vector_updated:
                                    logger.info("向量索引已同步更新")
                            except Exception as e:
                                # 向量索引更新失败不阻塞写入
                                vector_index_error = str(e)
                                logger.warning(
                                    f"向量索引更新失败（不影响日志写入）：{e}"
                                )

                        except (OSError, IOError, RuntimeError) as e:
                            # 索引更新失败，清理临时文件
                            logger.error(f"索引更新失败：{e}")
                            if temp_path.exists():
                                temp_path.unlink()
                            raise RuntimeError(f"索引更新失败，事务已回滚：{e}")

                    # 5. 所有操作成功，原子性重命名临时文件
                    temp_path.replace(journal_path)
                    logger.info(f"日志文件写入成功：{journal_path}")

                    # 记录结果
                    result["journal_path"] = str(journal_path)
                    result["monthly_abstract_updated"] = abstract_result
                    if abstract_error:
                        result["monthly_abstract_error"] = abstract_error
                    if vector_index_error:
                        result["vector_index_error"] = vector_index_error
                    result["updated_indices"] = updated_indices

                    if vector_index_error:
                        result["index_status"] = "degraded"
                    else:
                        result["index_status"] = "complete"

                    if abstract_success and not vector_index_error:
                        result["side_effects_status"] = "complete"
                    else:
                        result["side_effects_status"] = "degraded"

                except (OSError, IOError, RuntimeError):
                    # 确保临时文件被清理
                    if temp_path.exists():
                        temp_path.unlink()
                    raise

        except LockTimeoutError as e:
            # 锁超时，返回结构化错误
            logger.error(f"文件锁超时：{e}")
            return create_error_response(
                ErrorCode.LOCK_TIMEOUT,
                f"无法获取写入锁，请稍后重试：{e}",
                {
                    "lock_path": str(get_journals_lock_path()),
                    "timeout": FILE_LOCK_TIMEOUT_DEFAULT,
                },
                "等待几秒后重试，或检查是否有其他进程正在写入",
            )

        result["success"] = True

        # ===== 第三层：写入后确认 =====
        result["needs_confirmation"] = result["location_auto_filled"]
        if result["needs_confirmation"]:
            result["confirmation_message"] = (
                f"日志已保存至：{journal_path}\n\n"
                f"本次使用了默认地点：{location}\n"
                f"- 天气：{weather if weather else '（未获取）'}\n\n"
                f"如果这个地点不对，请告诉我正确地点。"
                f"我会基于新地点更新地点和天气。"
            )

    except (ValueError, IOError, RuntimeError, OSError) as e:
        logger.error(f"写入日志失败：{e}", exc_info=True)
        result["error"] = str(e)
        result["index_status"] = "not_started"
        result["side_effects_status"] = "not_started"

    # 添加性能指标
    timer.stop()
    result["metrics"] = timer.to_dict()

    if result["success"]:
        logger.info(f"写入完成，总耗时：{result['metrics'].get('total_ms', 0):.2f}ms")

    return result
