"""Summarization entry points using Google Gemini."""
from __future__ import annotations

import logging
import os
from dataclasses import dataclass
from datetime import datetime
import time
from typing import Iterator, List, Optional, Union

import google.generativeai as genai
from google.generativeai import GenerativeModel

from .web_content import ContentChunk, WebContent, fetch_web_or_pdf


@dataclass
class SummarizationResult:
    """Container for the response generated by Gemini.

    When summarizing chunked PDFs, ``summary`` will be empty and ``chunk_summaries``
    contains the per-chunk notes that were streamed.
    """

    summary: str
    source_type: str
    extracted_text: str
    image_count: int
    chunk_summaries: Optional[List[ChunkSummary]] = None


@dataclass
class ChunkSummary:
    """Represents the partial summary produced for a chunk of a PDF."""

    start_page: int
    end_page: int
    summary: str


StreamItem = Union[ChunkSummary, SummarizationResult]


DEFAULT_PROMPT = """Answer in English. You are a senior research analyst.
Provide a structured English summary based on both the textual and visual content supplied.
Please cover the following items:
1. Context — what the document or page is about and why it exists.
2. Key Findings — list three to five essential insights or messages.
3. Visual Interpretation — describe any notable charts, tables, or diagrams and explain their meaning.
4. Analyst Takeaways — offer two to three implications or recommendations.
5. Suggested Headline — propose a concise title for the material.

Write clearly and professionally as if preparing content for a briefing memo.
Date: {date}
"""

DEFAULT_PROMPT_JP = """日本語で回答してください。
あなたは上級リサーチアナリストです。
以下に与えられた**テキストおよびビジュアル（図表・画像など）**の内容に基づき、
以下の項目を必ず含めてください：
Context（背景） — そのドキュメントやページが何を扱っているのか、また存在理由（目的）は何か。
Key Findings（主要な発見・ポイント） — 3〜5個の重要な洞察やメッセージを箇条書きで。
Visual Interpretation（ビジュアルの解釈） — 図表・グラフ・表などに注目し、それが何を示しているかを説明。
Analyst Takeaways（アナリストの見解・示唆） — 2〜3個のインプリケーション（含意）または推奨事項を提示。
Suggested Headline（推奨タイトル） — 資料の内容を端的に表す簡潔な英語タイトルを提案。
文体はブリーフィングメモ（報告メモ）向けとして、明瞭かつ専門的に記述してください。
日付：{date}
"""

def _resolve_logger(logger: Optional[logging.Logger]) -> logging.Logger:
    if logger is not None:
        return logger
    default_logger = logging.getLogger("aiknowledge")
    if not default_logger.handlers:
        default_logger.addHandler(logging.NullHandler())
    return default_logger


def _build_prompt(date: datetime, custom_prompt: Optional[str], is_Japanese: bool=True) -> str:
    if custom_prompt:
        return custom_prompt
    if is_Japanese:
        return DEFAULT_PROMPT_JP.format(date=date.strftime("%Y-%m-%d"))
    else:
        return DEFAULT_PROMPT.format(date=date.strftime("%Y-%m-%d"))

def _build_parts(prompt: str, content: WebContent) -> List[object]:
    parts: List[object] = [prompt]
    for image_bytes in content.images:
        parts.append({"mime_type": "image/png", "data": image_bytes})
    text_payload = content.text if content.text.strip() else "No text could be extracted from the source."
    parts.append(
        f"Extracted text from {content.source_type} source (length={len(text_payload)} characters):\n{text_payload}"
    )
    return parts


def _build_chunk_parts(prompt: str, chunk: ContentChunk) -> List[object]:
    parts: List[object] = [prompt]
    for image_bytes in chunk.images:
        parts.append({"mime_type": "image/png", "data": image_bytes})
    text_payload = chunk.text if chunk.text.strip() else "No text could be extracted from the PDF pages."
    parts.append(
        "Text extracted from PDF pages {start}-{end} (length={length} characters):\n{text}".format(
            start=chunk.start_page,
            end=chunk.end_page,
            length=len(text_payload),
            text=text_payload,
        )
    )
    return parts


def _summarize_pdf_chunks(
    model: GenerativeModel,
    prompt_text: str,
    content: WebContent,
    logger: logging.Logger,
    *,
    min_gemini_interval: float,
) -> Iterator[ChunkSummary]:
    if not content.chunks:
        return

    last_request_time: Optional[float] = None
    for chunk in content.chunks:
        if last_request_time is not None and min_gemini_interval > 0:
            elapsed = time.time() - last_request_time
            if elapsed < min_gemini_interval:
                time.sleep(min_gemini_interval - elapsed)
        logger.debug(
            "Sending PDF chunk covering pages %d-%d to Gemini", chunk.start_page, chunk.end_page
        )
        request_started = time.time()
        chunk_parts = _build_chunk_parts(prompt_text, chunk)
        try:
            response = model.generate_content(chunk_parts)
        except Exception:
            elapsed = time.time() - request_started
            logger.exception(
                "Gemini request for pages %d-%d failed after %.2f seconds",
                chunk.start_page,
                chunk.end_page,
                elapsed,
            )
            raise
        elapsed = time.time() - request_started
        logger.debug(
            "Received Gemini response for pages %d-%d in %.2f seconds",
            chunk.start_page,
            chunk.end_page,
            elapsed,
        )
        note = (response.text or "").strip() or "No summary returned for these pages."
        yield ChunkSummary(
            start_page=chunk.start_page,
            end_page=chunk.end_page,
            summary=note,
        )
        last_request_time = time.time()


def _summarize_full_content(
    model: GenerativeModel,
    prompt_text: str,
    content: WebContent,
    logger: logging.Logger,
) -> str:
    parts = _build_parts(prompt_text, content)
    request_started = time.time()
    try:
        response = model.generate_content(parts)
    except Exception:
        elapsed = time.time() - request_started
        logger.exception("Gemini request failed after %.2f seconds", elapsed)
        raise
    elapsed = time.time() - request_started
    logger.debug("Received Gemini response in %.2f seconds", elapsed)
    return response.text or ""


def summarize_url_stream(
    url: str,
    *,
    google_api_key: Optional[str] = None,
    model_name: str = "gemini-2.5-flash",
    prompt: Optional[str] = None,
    logger: Optional[logging.Logger] = None,
    max_chars: int = 6000,
    max_image_mb: float = 4.0,
    max_pdf_pages: int = 300,
    pdf_page_group_size: int = 1,
    request_timeout: int = 20,
    is_Japanese: bool = True,
    min_gemini_interval: float = 2.0,
) -> Iterator[StreamItem]:
    """Stream partial and final summaries for the content of a URL using Gemini.

    Args:
        url: Public URL to a web page or PDF.
        google_api_key: API key for Google Gemini. Falls back to ``GOOGLE_API_KEY`` env var.
        model_name: Name of the Gemini model to use.
        prompt: Optional custom prompt template.
        logger: Optional :class:`logging.Logger`. A default logger is used when omitted.
        max_chars: Maximum number of characters to keep from extracted text.
        max_image_mb: Maximum size of each screenshot in megabytes.
        max_pdf_pages: Maximum number of PDF pages to process.
        pdf_page_group_size: Number of PDF pages to send in each Gemini request.
        request_timeout: Timeout (seconds) for HTTP requests.
        min_gemini_interval: Minimum number of seconds to wait between Gemini requests.

    Yields:
        :class:`ChunkSummary` objects for each processed PDF chunk (if applicable) and a
        final :class:`SummarizationResult`. For chunked PDFs the final result includes the
        streamed notes via ``chunk_summaries`` instead of an aggregated ``summary``.
    """

    resolved_logger = _resolve_logger(logger)
    resolved_logger.debug("Starting summarization for %s", url)

    if pdf_page_group_size <= 0:
        raise ValueError("pdf_page_group_size must be a positive integer.")

    content = fetch_web_or_pdf(
        url,
        logger=resolved_logger,
        max_chars=max_chars,
        max_image_mb=max_image_mb,
        max_pdf_pages=max_pdf_pages,
        pdf_page_group_size=pdf_page_group_size,
        request_timeout=request_timeout,
    )

    api_key = google_api_key or os.getenv("GOOGLE_API_KEY")
    if not api_key:
        raise ValueError("A Google API key must be provided via argument or the GOOGLE_API_KEY environment variable.")

    genai.configure(api_key=api_key)
    model = GenerativeModel(model_name)

    prompt_text = _build_prompt(datetime.utcnow(), prompt, is_Japanese)
    chunk_summaries: List[ChunkSummary] = []
    if content.source_type == "pdf":
        if content.chunks:
            for chunk_summary in _summarize_pdf_chunks(
                model,
                prompt_text,
                content,
                resolved_logger,
                min_gemini_interval=min_gemini_interval,
            ):
                chunk_summaries.append(chunk_summary)
                yield chunk_summary
            summary_text = ""
        else:
            summary_text = _summarize_full_content(
                model,
                prompt_text,
                content,
                resolved_logger,
            )
    else:
        parts = _build_parts(prompt_text, content)
        resolved_logger.debug("Sending %d parts to Gemini", len(parts))
        request_started = time.time()
        try:
            response = model.generate_content(parts)
        except Exception:
            elapsed = time.time() - request_started
            resolved_logger.exception(
                "Gemini request failed after %.2f seconds", elapsed
            )
            raise
        elapsed = time.time() - request_started
        resolved_logger.debug("Received Gemini response in %.2f seconds", elapsed)
        summary_text = response.text or ""

    resolved_logger.info("Finished summarization for %s", url)
    yield SummarizationResult(
        summary=summary_text.strip(),
        source_type=content.source_type,
        extracted_text=content.text,
        image_count=len(content.images),
        chunk_summaries=chunk_summaries or None,
    )


def summarize_url(
    url: str,
    *,
    google_api_key: Optional[str] = None,
    model_name: str = "gemini-2.5-flash",
    prompt: Optional[str] = None,
    logger: Optional[logging.Logger] = None,
    max_chars: int = 6000,
    max_image_mb: float = 4.0,
    max_pdf_pages: int = 300,
    pdf_page_group_size: int = 1,
    request_timeout: int = 20,
    is_Japanese: bool = True,
    min_gemini_interval: float = 2.0,
) -> SummarizationResult:
    """Summarize the content of a URL using Gemini.

    This convenience wrapper consumes :func:`summarize_url_stream` and returns only the final
    :class:`SummarizationResult` for callers that do not need chunk-by-chunk updates.
    """

    final_result: Optional[SummarizationResult] = None
    for item in summarize_url_stream(
        url,
        google_api_key=google_api_key,
        model_name=model_name,
        prompt=prompt,
        logger=logger,
        max_chars=max_chars,
        max_image_mb=max_image_mb,
        max_pdf_pages=max_pdf_pages,
        pdf_page_group_size=pdf_page_group_size,
        request_timeout=request_timeout,
        is_Japanese=is_Japanese,
        min_gemini_interval=min_gemini_interval,
    ):
        if isinstance(item, SummarizationResult):
            final_result = item

    if final_result is None:
        raise RuntimeError("Gemini did not return a final summary.")

    return final_result
