from html import escape
from io import BytesIO

from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.platypus import Flowable, HRFlowable, Paragraph, SimpleDocTemplate, Spacer

from app.models import Meeting, Recording, Transcript


def _format_transcript_timestamp(seconds: float | int | None) -> str:
    if seconds is None:
        return "00:00"
    total_seconds = max(0, int(seconds))
    minutes, secs = divmod(total_seconds, 60)
    return f"{minutes:02d}:{secs:02d}"


def _format_transcript_duration(duration_seconds: int | None) -> str:
    if not duration_seconds:
        return "00:00"
    total_seconds = max(0, int(duration_seconds))
    hours, remainder = divmod(total_seconds, 3600)
    minutes, secs = divmod(remainder, 60)
    if hours:
        return f"{hours:02d}:{minutes:02d}:{secs:02d}"
    return f"{minutes:02d}:{secs:02d}"


def _merge_transcript_segments(segments: list[dict], max_words_per_block: int = 150) -> list[dict]:
    merged_segments: list[dict] = []
    current_word_count = 0

    for item in segments:
        speaker = str(item.get("speaker") or "Unknown Speaker")
        text = str(item.get("text") or "").strip()
        if not text:
            continue
        start = item.get("start")
        end = item.get("end")
        words_in_text = len(text.split())

        # If same speaker AND not exceeding max words per block:
        if (
            merged_segments
            and merged_segments[-1]["speaker"] == speaker
            and (current_word_count + words_in_text <= max_words_per_block)
        ):
            merged_segments[-1]["text_parts"].append(text)
            merged_segments[-1]["end"] = end if end is not None else merged_segments[-1]["end"]
            current_word_count += words_in_text
            continue

        merged_segments.append({
            "speaker": speaker,
            "start": start,
            "end": end,
            "text_parts": [text],
        })
        current_word_count = words_in_text

    for item in merged_segments:
        item["text"] = "\n".join(item.pop("text_parts"))
    return merged_segments


def _build_speaker_color_map(participants: list[str], merged_segments: list[dict]) -> dict[str, colors.Color]:
    palette = [
        colors.HexColor("#2563EB"),
        colors.HexColor("#16A34A"),
        colors.HexColor("#7C3AED"),
        colors.HexColor("#EA580C"),
        colors.HexColor("#DB2777"),
        colors.HexColor("#0891B2"),
        colors.HexColor("#CA8A04"),
        colors.HexColor("#4F46E5"),
    ]
    speakers_in_order: list[str] = []
    for name in participants:
        speaker_name = str(name).strip()
        if speaker_name and speaker_name not in speakers_in_order:
            speakers_in_order.append(speaker_name)
    for item in merged_segments:
        speaker_name = item["speaker"]
        if speaker_name not in speakers_in_order:
            speakers_in_order.append(speaker_name)
    return {
        speaker_name: palette[index % len(palette)]
        for index, speaker_name in enumerate(speakers_in_order)
    }


class TranscriptBlock(Flowable):
    def __init__(
        self,
        speaker: str,
        color: colors.Color,
        start: float | int | None,
        end: float | int | None,
        text: str,
        styles: dict[str, ParagraphStyle],
        is_continuation: bool = False,
        body_paragraph: Paragraph | None = None,
    ):
        super().__init__()
        self.speaker = speaker
        self.color = color
        self.start = start
        self.end = end
        self.styles = styles
        self.is_continuation = is_continuation

        if not is_continuation:
            timestamp = f"{_format_transcript_timestamp(start)} \u2192 {_format_transcript_timestamp(end)}"
            header_html = (
                f'<font color="{color.hexval()}"><b>{escape(speaker)}</b></font> '
                f'<font color="#6B7280" size="9">{escape(timestamp)}</font>'
            )
            self.header = Paragraph(header_html, styles["transcript_header"])
        else:
            self.header = None

        if body_paragraph is not None:
            self.body = body_paragraph
        else:
            body_html = escape(text).replace("\n", "<br/>")
            self.body = Paragraph(body_html, styles["transcript_body"])

        self.left_padding = 14
        self.top_padding = 2
        self.bottom_padding = 2
        self.block_gap = 10
        self._header_height = 0
        self._body_height = 0

    def wrap(self, avail_width, avail_height):
        text_width = max(10, avail_width - self.left_padding)
        header_gap = 0
        if self.header:
            _, self._header_height = self.header.wrap(text_width, avail_height)
            header_gap = 4
        else:
            self._header_height = 0

        _, self._body_height = self.body.wrap(text_width, avail_height)
        total_height = self.top_padding + self._header_height + header_gap + self._body_height + self.bottom_padding + self.block_gap
        return avail_width, total_height

    def split(self, avail_width, avail_height):
        text_width = max(10, avail_width - self.left_padding)
        header_h = 0
        header_gap = 0
        if self.header:
            _, header_h = self.header.wrap(text_width, avail_height)
            header_gap = 4

        overhead = self.top_padding + header_h + header_gap + self.bottom_padding + self.block_gap
        available_body_height = avail_height - overhead

        # Need space for at least 1-2 lines of text (~25 pt)
        if available_body_height < 25:
            return []

        split_body = self.body.split(text_width, available_body_height)
        if not split_body or len(split_body) < 2:
            return []

        part1 = TranscriptBlock(
            speaker=self.speaker,
            color=self.color,
            start=self.start,
            end=self.end,
            text="",
            styles=self.styles,
            is_continuation=self.is_continuation,
            body_paragraph=split_body[0],
        )
        part1.header = self.header

        part2 = TranscriptBlock(
            speaker=self.speaker,
            color=self.color,
            start=self.start,
            end=self.end,
            text="",
            styles=self.styles,
            is_continuation=True,
            body_paragraph=split_body[1],
        )

        return [part1, part2]

    def draw(self):
        self.canv.saveState()
        header_gap = 4 if self.header else 0
        content_height = self.top_padding + self._header_height + header_gap + self._body_height + self.bottom_padding
        self.canv.setStrokeColor(self.color)
        self.canv.setLineWidth(2)
        self.canv.line(2, self.block_gap, 2, self.block_gap + content_height)
        if self.header:
            header_y = self.block_gap + self.bottom_padding + self._body_height + header_gap
            self.header.drawOn(self.canv, self.left_padding, header_y)
        self.body.drawOn(self.canv, self.left_padding, self.block_gap + self.bottom_padding)
        self.canv.restoreState()


def build_transcript_pdf(
    title: str,
    meeting_id: int,
    transcript: Transcript,
    meeting: Meeting,
    recording: Recording | None,
    participants: list[str],
    segments: list[dict],
) -> bytes:
    merged_segments = _merge_transcript_segments(segments)
    speaker_color_map = _build_speaker_color_map(participants, merged_segments)
    transcript_date = (
        meeting.scheduled_at
        or (recording.started_at if recording else None)
        or transcript.completed_at
        or transcript.created_at
    )
    formatted_date = transcript_date.strftime("%b %d, %Y %I:%M %p") if transcript_date else "N/A"
    duration_value = transcript.duration_seconds
    if not duration_value and recording and recording.started_at and recording.stopped_at:
        duration_value = int((recording.stopped_at - recording.started_at).total_seconds())

    participant_names = participants or list(speaker_color_map.keys())

    buffer = BytesIO()
    doc = SimpleDocTemplate(
        buffer,
        pagesize=A4,
        leftMargin=20 * mm,
        rightMargin=20 * mm,
        topMargin=20 * mm,
        bottomMargin=20 * mm,
        title=title or f"Meeting {meeting_id}",
    )

    sample_styles = getSampleStyleSheet()
    styles = {
        "title": ParagraphStyle(
            "TranscriptTitle",
            parent=sample_styles["Title"],
            fontName="Helvetica-Bold",
            fontSize=20,
            leading=24,
            textColor=colors.HexColor("#0F172A"),
            spaceAfter=4,
        ),
        "meta": ParagraphStyle(
            "TranscriptMeta",
            parent=sample_styles["BodyText"],
            fontName="Helvetica",
            fontSize=9.5,
            leading=13,
            textColor=colors.HexColor("#475569"),
            spaceAfter=2,
        ),
        "section": ParagraphStyle(
            "TranscriptSection",
            parent=sample_styles["Heading3"],
            fontName="Helvetica-Bold",
            fontSize=11,
            leading=14,
            textColor=colors.HexColor("#0F172A"),
            spaceAfter=8,
        ),
        "participants_line": ParagraphStyle(
            "ParticipantsLine",
            parent=sample_styles["BodyText"],
            fontName="Helvetica",
            fontSize=10,
            leading=14,
            textColor=colors.HexColor("#334155"),
            spaceAfter=4,
        ),
        "transcript_header": ParagraphStyle(
            "TranscriptHeader",
            parent=sample_styles["BodyText"],
            fontName="Helvetica",
            fontSize=11,
            leading=13,
            textColor=colors.HexColor("#0F172A"),
        ),
        "transcript_body": ParagraphStyle(
            "TranscriptBody",
            parent=sample_styles["BodyText"],
            fontName="Helvetica",
            fontSize=10.5,
            leading=15,
            textColor=colors.black,
        ),
        "empty": ParagraphStyle(
            "TranscriptEmpty",
            parent=sample_styles["BodyText"],
            fontName="Helvetica",
            fontSize=10,
            leading=14,
            textColor=colors.HexColor("#475569"),
        ),
    }

    meta_line = (
        f"<b>Date:</b> {escape(formatted_date)}"
        f"&nbsp;&nbsp;&nbsp;&nbsp;<b>Duration:</b> {escape(_format_transcript_duration(duration_value))}"
        f"&nbsp;&nbsp;&nbsp;&nbsp;<b>Participants:</b> {len(participant_names)}"
    )
    participant_line = " &nbsp;&nbsp; ".join(
        f'<font color="{speaker_color_map.get(name, colors.HexColor("#475569")).hexval()}"><b>{escape(name)}</b></font>'
        for name in participant_names
    )

    story = [
        Paragraph(escape(title or f"Meeting {meeting_id}"), styles["title"]),
        Paragraph(meta_line, styles["meta"]),
        Spacer(1, 4),
        HRFlowable(width="100%", thickness=0.8, color=colors.HexColor("#CBD5E1")),
        Spacer(1, 12),
        Paragraph("Participants:", styles["section"]),
    ]

    if participant_names:
        story.append(Paragraph(participant_line, styles["participants_line"]))
    else:
        story.append(Paragraph("No participants available.", styles["empty"]))

    story.append(Spacer(1, 12))
    story.append(Paragraph("Transcript:", styles["section"]))

    if merged_segments:
        for item in merged_segments:
            story.append(
                TranscriptBlock(
                    speaker=item["speaker"],
                    color=speaker_color_map.get(item["speaker"], colors.HexColor("#475569")),
                    start=item.get("start"),
                    end=item.get("end"),
                    text=item.get("text", ""),
                    styles=styles,
                )
            )
    else:
        story.append(Paragraph("No transcript content available.", styles["empty"]))

    doc.build(story)
    return buffer.getvalue()
