"""
事件服务模块 - 处理事件提取和日历导出

主要功能：
- 从转录文本中提取日历事件
- 生成 ICS 日历文件内容
- 处理 iCalendar 格式的特殊字符转义
"""
import json
import logging
from datetime import datetime

from src.clients import db
from src.models import Recording, Event
from src.services.llm_service import call_llm_completion
from src.clients import TEXT_MODEL_NAME
from src.utils.json_utils import safe_json_loads

logger = logging.getLogger(__name__)


def extract_events_from_transcript(recording_id, transcript_text, summary_text):
    """
    从转录文本中使用 LLM 提取日历事件
    
    Args:
        recording_id: 录音 ID
        transcript_text: 格式化后的转录文本
        summary_text: 生成的摘要文本
    """
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording or not recording.owner or not recording.owner.extract_events:
            return  # 该用户未启用事件提取功能

        logger.info(f"正在为录音 {recording_id} 提取事件")

        # 构建全面的上下文信息
        current_date = datetime.now()
        context_parts = []

        # 关键：确定相对日期计算的参考日期
        reference_date = None
        reference_date_source = ""

        if recording.meeting_date:
            # 优先使用会议日期（如果可用）
            reference_date = recording.meeting_date
            reference_date_source = "会议日期"
            context_parts.append(f"**会议日期（用于相对日期计算）: {recording.meeting_date.strftime('%A, %B %d, %Y')}**")
        elif recording.created_at:
            # 回退到上传日期
            reference_date = recording.created_at.date()
            reference_date_source = "上传日期（无会议日期）"
            context_parts.append(f"**参考日期（用于相对日期计算）: {recording.created_at.strftime('%A, %B %d, %Y')}**")

        context_parts.append(f"今天的实际日期: {current_date.strftime('%A, %B %d, %Y')}")
        context_parts.append(f"当前时间: {current_date.strftime('%I:%M %p')}")

        # 添加额外的录音上下文
        if recording.created_at:
            context_parts.append(f"录音上传时间: {recording.created_at.strftime('%B %d, %Y at %I:%M %p')}")
        if recording.meeting_date and reference_date_source == "会议日期":
            # 计算会议与今天之间的天数，提供上下文
            days_since = (current_date.date() - recording.meeting_date).days
            if days_since == 0:
                context_parts.append("这个会议发生在今天")
            elif days_since == 1:
                context_parts.append("这个会议发生在昨天")
            else:
                context_parts.append(f"这个会议发生在 {days_since} 天前")

        # 添加用户上下文以便更好地理解
        if recording.owner:
            user_context = []
            if recording.owner.name:
                user_context.append(f"用户姓名: {recording.owner.name}")
            if recording.owner.job_title:
                user_context.append(f"职位: {recording.owner.job_title}")
            if recording.owner.company:
                user_context.append(f"公司: {recording.owner.company}")
            if user_context:
                context_parts.append("用户信息: " + ", ".join(user_context))

        # 添加参与者信息（如果可用）
        if recording.participants:
            context_parts.append(f"会议参与者: {recording.participants}")

        context_section = "\n".join(context_parts)

        # 准备事件提取的提示词
        event_prompt = f"""你正在分析会议转录以提取日历事件。请使用以下上下文正确解释相对日期和时间。

重要上下文:
{context_section}

说明:
1. **关键**: 使用上面显示的会议日期作为所有相对日期计算的参考点
2. 当人们说"下周三"或"明天"或"下周"时，从会议日期计算，而不是从今天计算
3. 示例: 如果会议日期是 2025年9月13日，有人说"下周三"，那就是 2025年9月17日
4. 如果未提及事件的具体时间，使用 09:00:00（上午9点）作为默认开始时间
5. 注意提及的时区
6. 仅提取明确讨论为未来预约、会议或截止日期的事件
7. 不要为过去的事件或一般讨论创建事件

对于找到的每个事件，提取:
- 标题: 事件的清晰简洁标题
- 描述: 包含会议上下文的简要描述
- 开始日期/时间: 计算出的实际日期/时间（ISO 格式 YYYY-MM-DDTHH:MM:SS，如果未指定时间则使用 09:00:00）
- 结束日期/时间: 事件结束时间（如果提及，ISO 格式，如果未指定则默认为开始后1小时）
- 地点: 事件发生的地点（如果提及）
- 参与者: 应该参加的人员列表（如果提及）
- 提醒分钟: 提前多少分钟提醒（默认15分钟）

转录摘要:
{summary_text}

转录摘录（用于额外上下文）:
{transcript_text[:8000]}

响应格式:
响应一个包含 "events" 数组的 JSON 对象。如果未找到事件，返回一个空 events 数组的 JSON 对象。

示例响应:
{{
  "events": [
    {{
      "title": "项目评审会议",
      "description": "季度评审，讨论项目进展和下一步计划，如会议中所讨论",
      "start_datetime": "2025-07-22T14:00:00",
      "end_datetime": "2025-07-22T15:30:00",
      "location": "会议室 A",
      "attendees": ["张三", "李四", "王五"],
      "reminder_minutes": 15
    }}
  ]
}}

关键规则:
1. **基于上面上下文中提供的会议日期进行所有日期计算**
2. 仅提取相对于会议日期为未来的事件（不是今天的日期）
3. 使用会议日期作为参考点转换所有相对日期
4. 示例: 如果会议日期是 2025年9月13日（星期五），有人说:
   - "下周三" = 2025年9月17日
   - "明天" = 2025年9月14日
   - "下周" = 2025年9月15-19日这一周
5. 重要: 如果未提及时间，始终使用 09:00:00（上午9点）作为开始时间，而不是午夜
6. 在描述中包含讨论的上下文
7. 不要发明或假设未明确讨论的事件
8. 如果对日期/时间不确定，不要包含该事件"""

        completion = call_llm_completion(
            model_name=TEXT_MODEL_NAME,
            messages=[
                {"role": "system", "content": """你是从会议转录中提取日历事件的专家。你擅长:
1. 理解相对日期引用（"下周二"、"明天"、"两周后"）并将其转换为绝对日期
2. 从对话中识别真正的未来预约、会议和截止日期
3. 区分实际计划的事件与一般讨论
4. 准确提取参与者姓名和会议详情

你必须仅以有效的 JSON 格式响应。"""},
                {"role": "user", "content": event_prompt}
            ],
            temperature=0.2,
            response_format={"type": "json_object"},
            max_tokens=3000
        )

        response_content = completion.choices[0].message.content
        events_data = safe_json_loads(response_content, {})

        # 处理 {"events": [...]} 和直接数组格式
        if isinstance(events_data, dict) and 'events' in events_data:
            events_list = events_data['events']
        elif isinstance(events_data, list):
            events_list = events_data
        else:
            events_list = []

        logger.info(f"为录音 {recording_id} 找到 {len(events_list)} 个事件")

        # 将事件保存到数据库
        for event_data in events_list:
            try:
                # 解析日期
                start_dt = None
                end_dt = None

                if 'start_datetime' in event_data:
                    try:
                        # 首先尝试 ISO 格式
                        start_dt = datetime.fromisoformat(event_data['start_datetime'].replace('Z', '+00:00'))
                    except:
                        # 尝试其他常见格式
                        from dateutil import parser
                        try:
                            start_dt = parser.parse(event_data['start_datetime'])
                        except:
                            logger.warning(f"无法解析 start_datetime: {event_data['start_datetime']}")
                            continue  # 如果无法解析日期，跳过此事件

                if 'end_datetime' in event_data and event_data['end_datetime']:
                    try:
                        end_dt = datetime.fromisoformat(event_data['end_datetime'].replace('Z', '+00:00'))
                    except:
                        from dateutil import parser
                        try:
                            end_dt = parser.parse(event_data['end_datetime'])
                        except:
                            pass  # 结束时间是可选的

                # 创建事件记录
                event = Event(
                    recording_id=recording_id,
                    title=event_data.get('title', '无标题事件')[:200],
                    description=event_data.get('description', ''),
                    start_datetime=start_dt,
                    end_datetime=end_dt,
                    location=event_data.get('location', '')[:500] if event_data.get('location') else None,
                    attendees=json.dumps(event_data.get('attendees', [])) if event_data.get('attendees') else None,
                    reminder_minutes=event_data.get('reminder_minutes', 15)
                )

                db.session.add(event)
                logger.info(f"为录音 {recording_id} 添加事件 '{event.title}'")

            except Exception as e:
                logger.error(f"为录音 {recording_id} 保存事件时出错: {str(e)}")
                continue

        db.session.commit()

        # 刷新录音以确保加载事件关系
        recording = db.session.get(Recording, recording_id)
        if recording:
            db.session.refresh(recording)

    except Exception as e:
        logger.error(f"为录音 {recording_id} 提取事件时出错: {str(e)}")
        db.session.rollback()


def generate_ics_content(events):
    """
    为一个或多个事件生成 ICS 日历文件内容
    
    Args:
        events: 事件对象或事件对象列表
    
    Returns:
        str: ICS 格式的日历内容
    """
    import uuid
    from datetime import datetime, timedelta

    # 统一处理为列表
    if not isinstance(events, list):
        events = [events]
    
    if not events:
        return ''

    # 开始构建 ICS 内容
    lines = [
        'BEGIN:VCALENDAR',
        'VERSION:2.0',
        'PRODID:-//Speakr//Event Export//EN',
        'CALSCALE:GREGORIAN',
        'METHOD:PUBLISH',
    ]

    for event in events:
        # 为每个事件生成唯一 ID
        uid = f"{event.id}-{uuid.uuid4()}@speakr.app"

        # 以 iCalendar 格式格式化日期（YYYYMMDDTHHMMSS）
        def format_ical_date(dt):
            if dt:
                return dt.strftime('%Y%m%dT%H%M%S')
            return None

        lines.extend([
            'BEGIN:VEVENT',
            f'UID:{uid}',
            f'DTSTAMP:{format_ical_date(datetime.utcnow())}',
        ])

        # 添加事件详情
        if event.start_datetime:
            lines.append(f'DTSTART:{format_ical_date(event.start_datetime)}')

        if event.end_datetime:
            lines.append(f'DTEND:{format_ical_date(event.end_datetime)}')
        elif event.start_datetime:
            # 如果没有结束时间，默认为开始后1小时
            end_time = event.start_datetime + timedelta(hours=1)
            lines.append(f'DTEND:{format_ical_date(end_time)}')

        # 添加标题和描述
        lines.append(f'SUMMARY:{escape_ical_text(event.title)}')

        if event.description:
            lines.append(f'DESCRIPTION:{escape_ical_text(event.description)}')

        # 添加地点（如果可用）
        if event.location:
            lines.append(f'LOCATION:{escape_ical_text(event.location)}')

        # 添加参与者（如果可用）
        if event.attendees:
            try:
                attendees_list = json.loads(event.attendees)
                for attendee in attendees_list:
                    if attendee:
                        lines.append(f'ATTENDEE;CN={escape_ical_text(attendee)}:mailto:{attendee.replace(" ", ".").lower()}@example.com')
            except:
                pass

        # 添加提醒/闹钟（如果指定）
        if event.reminder_minutes and event.reminder_minutes > 0:
            lines.extend([
                'BEGIN:VALARM',
                'TRIGGER:-PT{}M'.format(event.reminder_minutes),
                'ACTION:DISPLAY',
                f'DESCRIPTION:提醒: {escape_ical_text(event.title)}',
                'END:VALARM'
            ])

        # 关闭事件
        lines.extend([
            'STATUS:CONFIRMED',
            'TRANSP:OPAQUE',
            'END:VEVENT',
        ])

    # 关闭日历
    lines.append('END:VCALENDAR')

    return '\r\n'.join(lines)


def escape_ical_text(text):
    """
    转义 iCalendar 格式的特殊字符
    
    Args:
        text: 需要转义的文本
    
    Returns:
        str: 转义后的文本
    """
    if not text:
        return ''
    # 转义特殊字符
    text = str(text)
    text = text.replace('\\', '\\\\')
    text = text.replace(',', '\\,')
    text = text.replace(';', '\\;')
    text = text.replace('\n', '\\n')
    return text
