"""Shared parsing utilities for summarization providers."""

from __future__ import annotations

import json
from datetime import UTC, datetime
from typing import TYPE_CHECKING, TypedDict, cast

from noteflow.domain.entities import ActionItem, KeyPoint, Summary
from noteflow.domain.summarization import InvalidResponseError
from noteflow.infrastructure.logging import get_logger

if TYPE_CHECKING:
    from collections.abc import Sequence

    from noteflow.domain.entities import Segment
    from noteflow.domain.summarization import SummarizationRequest

_logger = get_logger(__name__)


class _KeyPointData(TypedDict, total=False):
    """Expected structure for key point data from LLM response."""

    text: str
    segment_ids: list[int]


class _ActionItemData(TypedDict, total=False):
    """Expected structure for action item data from LLM response."""

    text: str
    assignee: str
    priority: int | str
    segment_ids: list[int]


class _LLMResponseData(TypedDict, total=False):
    """Expected structure for LLM response JSON."""

    executive_summary: str
    key_points: list[_KeyPointData]
    action_items: list[_ActionItemData]

_TONE_INSTRUCTIONS: dict[str, str] = {
    "professional": "Use formal, business-appropriate language.",
    "casual": "Use conversational, approachable language.",
    "technical": "Use precise technical terminology.",
    "friendly": "Use warm, personable language.",
}

_FORMAT_INSTRUCTIONS: dict[str, str] = {
    "bullet_points": "Present information in bullet points.",
    "narrative": "Write in flowing paragraphs.",
    "structured": "Use headers and organized sections.",
    "concise": "Be extremely brief and to the point.",
}

_VERBOSITY_INSTRUCTIONS: dict[str, str] = {
    "minimal": "Provide only essential information.",
    "balanced": "Include moderate detail.",
    "detailed": "Include comprehensive information.",
    "comprehensive": "Include all relevant details and context.",
}


def build_style_prompt(
    tone: str | None = None,
    format_style: str | None = None,
    verbosity: str | None = None,
) -> str:
    """Build style instruction string from template preferences.

    Args:
        tone: Desired tone (professional, casual, technical, friendly).
        format_style: Desired format (bullet_points, narrative, structured, concise).
        verbosity: Desired verbosity (minimal, balanced, detailed, comprehensive).

    Returns:
        Style instruction string to prepend to system prompt, or empty string.
    """
    parts: list[str] = []

    if tone:
        if tone in _TONE_INSTRUCTIONS:
            parts.append(_TONE_INSTRUCTIONS[tone])
        else:
            _logger.debug("Unknown tone value ignored: %s", tone)

    if format_style:
        if format_style in _FORMAT_INSTRUCTIONS:
            parts.append(_FORMAT_INSTRUCTIONS[format_style])
        else:
            _logger.debug("Unknown format value ignored: %s", format_style)

    if verbosity:
        if verbosity in _VERBOSITY_INSTRUCTIONS:
            parts.append(_VERBOSITY_INSTRUCTIONS[verbosity])
        else:
            _logger.debug("Unknown verbosity value ignored: %s", verbosity)

    result = " ".join(parts)
    if result:
        _logger.debug("Built style prompt: %s", result)
    return result


# System prompt for structured summarization
SYSTEM_PROMPT = """You are a meeting summarization assistant. Analyze the transcript and produce structured output.

OUTPUT FORMAT (JSON):
{
  "executive_summary": "2-3 sentence high-level overview",
  "key_points": [
    {"text": "Key insight or decision", "segment_ids": [0, 1]}
  ],
  "action_items": [
    {"text": "Action to take", "assignee": "Person name or empty string", "priority": 0, "segment_ids": [2]}
  ]
}

RULES:
1. Each key_point and action_item MUST have at least one segment_id referencing the source
2. segment_ids are integers matching the [N] markers in the transcript
3. priority: 0=unspecified, 1=low, 2=medium, 3=high
4. Only extract action items that clearly indicate tasks to be done
5. Output ONLY valid JSON, no markdown or explanation"""


def build_transcript_prompt(request: SummarizationRequest) -> str:
    """Build transcript prompt with segment markers.

    Args:
        request: Summarization request with segments.

    Returns:
        Formatted prompt string with transcript and constraints.
    """
    lines = [f"[{seg.segment_id}] {seg.text}" for seg in request.segments]
    constraints = ""
    if request.segments:
        valid_ids = ", ".join(str(seg.segment_id) for seg in request.segments)
        constraints = (
            "\n\nCONSTRAINTS:\n"
            f"- Maximum {request.max_key_points} key points\n"
            f"- Maximum {request.max_action_items} action items\n"
            f"- Valid segment_ids: {valid_ids}"
        )

    return f"TRANSCRIPT:\n{chr(10).join(lines)}{constraints}"


def _strip_markdown_fences(text: str) -> str:
    """Remove markdown code block delimiters from text.

    LLM responses often wrap JSON in ```json...``` blocks.
    This strips those delimiters for clean parsing.

    Args:
        text: Raw response text, possibly with markdown fences.

    Returns:
        Text with markdown code fences removed.
    """
    text = text.strip()
    if not text.startswith("```"):
        return text

    lines = text.split("\n")
    # Skip opening fence (e.g., "```json" or "```")
    if lines[0].startswith("```"):
        lines = lines[1:]
    # Skip closing fence
    if lines and lines[-1].strip() == "```":
        lines = lines[:-1]
    return "\n".join(lines)


def _parse_key_point(
    data: _KeyPointData,
    valid_ids: set[int],
    segments: Sequence[Segment],
) -> KeyPoint:
    """Parse a single key point from LLM response data.

    Args:
        data: Raw key point dictionary from JSON response.
        valid_ids: Set of valid segment IDs for validation.
        segments: Segment data for timestamp lookup.

    Returns:
        Parsed KeyPoint entity.
    """
    seg_ids = [sid for sid in data.get("segment_ids", []) if sid in valid_ids]
    start_time = 0.0
    end_time = 0.0
    if seg_ids and (refs := [s for s in segments if s.segment_id in seg_ids]):
        start_time = min(s.start_time for s in refs)
        end_time = max(s.end_time for s in refs)
    return KeyPoint(
        text=str(data.get("text", "")),
        segment_ids=seg_ids,
        start_time=start_time,
        end_time=end_time,
    )


def _parse_action_item(data: _ActionItemData, valid_ids: set[int]) -> ActionItem:
    """Parse a single action item from LLM response data.

    Args:
        data: Raw action item dictionary from JSON response.
        valid_ids: Set of valid segment IDs for validation.

    Returns:
        Parsed ActionItem entity.
    """
    seg_ids = [sid for sid in data.get("segment_ids", []) if sid in valid_ids]
    priority_raw = data.get("priority", 0)
    priority = priority_raw if isinstance(priority_raw, int) else 0
    if priority not in range(4):
        priority = 0
    return ActionItem(
        text=str(data.get("text", "")),
        assignee=str(data.get("assignee", "")),
        priority=priority,
        segment_ids=seg_ids,
    )


def parse_llm_response(response_text: str, request: SummarizationRequest) -> Summary:
    """Parse JSON response into Summary entity.

    Args:
        response_text: Raw JSON response from LLM.
        request: Original request for validation context.

    Returns:
        Summary entity with parsed data.

    Raises:
        InvalidResponseError: If JSON is malformed.
    """
    text = _strip_markdown_fences(response_text)

    try:
        data = cast(_LLMResponseData, json.loads(text))
    except json.JSONDecodeError as e:
        raise InvalidResponseError(f"Invalid JSON response: {e}") from e

    valid_ids = {seg.segment_id for seg in request.segments}

    # Parse key points and action items using helper functions
    key_points = [
        _parse_key_point(kp_data, valid_ids, request.segments)
        for kp_data in data.get("key_points", [])[: request.max_key_points]
    ]
    action_items = [
        _parse_action_item(ai_data, valid_ids)
        for ai_data in data.get("action_items", [])[: request.max_action_items]
    ]

    return Summary(
        meeting_id=request.meeting_id,
        executive_summary=str(data.get("executive_summary", "")),
        key_points=key_points,
        action_items=action_items,
        generated_at=datetime.now(UTC),
    )
