import json
from datetime import timedelta
import logging
import typer
from rich import print

# NOTE: Heavy / optional imports (LLM, OpenAI) are intentionally deferred until inside
# command functions to avoid slow startup or hangs when optional deps are problematic.
from .api import ingest as api_ingest, dissect as api_dissect, expand as api_expand, stream as api_stream
from .models.core import GranularitySpec
from .store_io import write_json

app = typer.Typer(add_completion=False)


def _coerce_level(val: str) -> int:
    mapping = {
        'critical': logging.CRITICAL,
        'error': logging.ERROR,
        'warning': logging.WARNING,
        'warn': logging.WARNING,
        'info': logging.INFO,
        'debug': logging.DEBUG,
        'notset': logging.NOTSET,
    }
    return mapping.get(val.lower(), logging.INFO)


@app.callback()
def main(
    ctx: typer.Context,
    log_level: str = typer.Option(
        None,
        '--log-level',
        help='Logging level (DEBUG, INFO, WARNING, ERROR). Overrides GRANULE_DEBUG flag.'
    ),
    debug: bool = typer.Option(
        False,
        '--debug',
        help='Shortcut for --log-level DEBUG'
    ),
):
    """Granule CLI entrypoint configuring logging early.

    Logging hierarchy: command line flag > GRANULE_DEBUG env (handled in config import) > default INFO.
    """
    # Acquire existing logger created in config (import side-effect ensures basicConfig set once)
    logger = logging.getLogger('granule')
    effective_level = None
    if debug:
        effective_level = logging.DEBUG
    elif log_level:
        effective_level = _coerce_level(log_level)
    if effective_level is not None:
        logger.setLevel(effective_level)
        # Also update handlers attached to root/basicConfig
        for h in logging.getLogger().handlers:
            try:
                h.setLevel(effective_level)
            except Exception:  # pragma: no cover
                pass
        logger.debug('Logging configured (level=%s) via CLI flags', logging.getLevelName(effective_level))
    else:
        logger.debug('Logging configured using existing level=%s', logging.getLevelName(logger.level))
    ctx.obj = ctx.obj or {}
    ctx.obj['logger'] = logger

@app.command()
def ingest(
    source: str,
    kind: str = "blog",
    output: str = typer.Option("doc.json", "--output", "-o", help="Output JSON path"),
):
    log = logging.getLogger('granule.cli')
    doc = api_ingest(source, kind)
    write_json(doc, output)
    print(f"[green]Wrote[/green] {output}")
    log.info("Ingested source kind=%s output=%s", kind, output)

@app.command()
def dissect(
    doc_path: str,
    max_tokens: int = 80,
    only: str = "",
    output: str = typer.Option("atoms.json", "--output", "-o", help="Output JSON path"),
):
    log = logging.getLogger('granule.cli')
    data = json.load(open(doc_path, "r", encoding="utf-8"))
    from .models.core import StructuredDoc
    doc = StructuredDoc(**data)
    kinds = [k.strip() for k in only.split(",") if k.strip()] or None
    spec = GranularitySpec(max_tokens_per_atom=max_tokens, atom_types=kinds)
    graph = api_dissect(doc, spec)
    write_json(graph, output)
    print(f"[green]Wrote[/green] {output}")
    log.info("Dissected doc=%s kinds=%s output=%s", doc_path, kinds, output)

@app.command()
def expand(
    atoms_path: str,
    target: str = "55s",
    output: str = typer.Option("unit.json", "--output", "-o", help="Output JSON path"),
):
    log = logging.getLogger('granule.cli')
    data = json.load(open(atoms_path, "r", encoding="utf-8"))
    from .models.core import AtomGraph, GranularitySpec
    graph = AtomGraph(**data)
    td = parse_duration(target)
    spec = GranularitySpec(target_duration=td)
    unit = api_expand(graph, spec)
    write_json(unit, output)
    print(f"[green]Wrote[/green] {output}")
    log.info("Expanded atoms=%s target=%s output=%s", atoms_path, target, output)

@app.command()
def stream(
    atoms_path: str,
    pace: str = "55s",
    until: str = "10m",
    output: str = typer.Option("session.jsonl", "--output", "-o", help="Output JSONL path"),
):
    log = logging.getLogger('granule.cli')
    data = json.load(open(atoms_path, "r", encoding="utf-8"))
    from .models.core import AtomGraph
    graph = AtomGraph(**data)
    pace_td = parse_duration(pace).total_seconds()
    until_td = parse_duration(until).total_seconds()
    with open(output, "w", encoding="utf-8") as f:
        for item in api_stream(graph, pace_td, until_td):
            f.write(json.dumps(item, ensure_ascii=False) + "\n")
    print(f"[green]Wrote[/green] {output}")
    log.info("Streamed session atoms=%s pace=%s until=%s output=%s", atoms_path, pace, until, output)

def parse_duration(s: str) -> timedelta:
    s = s.strip().lower()
    if s.endswith("s"): return timedelta(seconds=float(s[:-1]))
    if s.endswith("m"): return timedelta(minutes=float(s[:-1]))
    if s.endswith("h"): return timedelta(hours=float(s[:-1]))
    raise typer.BadParameter("Duration must end with s/m/h")

@app.command("simple-youtube")
def simple_youtube(url: str, output: str = typer.Option("simple.json", "--output", "-o")):
    """Run simplified pipeline: YouTube URL -> LLM summary."""
    from .api_simple import simple_youtube_summary  # lazy import
    result = simple_youtube_summary(url)
    write_json(result, output)
    print(f"[green]Wrote[/green] {output}")
    logging.getLogger('granule.cli').info("Simple YouTube summary url=%s output=%s", url, output)


@app.command("simple-card")
def simple_card(url: str, output: str = typer.Option("card.json", "--output", "-o")):
    """YouTube URL -> Transcript Insight Card JSON (LLM if enabled)."""
    from .api_simple import simple_youtube_card  # lazy import
    card = simple_youtube_card(url)
    write_json(card, output)
    print(f"[green]Wrote[/green] {output}")
    logging.getLogger('granule.cli').info("Simple card url=%s output=%s", url, output)

@app.command("video-card")
def video_card(
    url: str,
    output: str = typer.Option("video_card.json", "--output", "-o"),
    title: str = typer.Option(None, "--title", help="Override video title (skip fetch)"),
):
    """YouTube URL -> VideoInsightCardPayload JSON (strict schema). Optionally override title."""
    from .api_simple import video_insight_card  # lazy import
    card = video_insight_card(url, title=title)
    from pydantic import BaseModel
    if isinstance(card, BaseModel):
        payload = card.model_dump(mode="python")
    else:  # pragma: no cover
        payload = card
    write_json(payload, output)
    print(f"[green]Wrote[/green] {output}")
    logging.getLogger('granule.cli').info("Video insight card url=%s title=%s output=%s", url, title, output)

@app.command("text-video-card")
def text_video_card(
    input_path: str = typer.Argument(..., help="Path to .txt file OR raw text snippet"),
    output: str = typer.Option("text_video_card.json", "--output", "-o"),
    title: str = typer.Option(None, "--title", help="Set title for the generated card"),
    assume_file: bool = typer.Option(False, "--file", help="Force treat input as path even if short"),
):
    """Raw transcript text (or a .txt file) -> VideoInsightCardPayload JSON.

    If input_path points to an existing file (or --file is set) the contents are read;
    otherwise the argument itself is treated as the transcript text.
    """
    import os
    if (assume_file or os.path.exists(input_path)) and os.path.isfile(input_path):
        text = open(input_path, 'r', encoding='utf-8').read()
    else:
        text = input_path
    from .api_simple import text_video_insight_card  # lazy import
    card = text_video_insight_card(text, title=title)
    from pydantic import BaseModel
    payload = card.model_dump(mode='python') if isinstance(card, BaseModel) else card
    write_json(payload, output)
    print(f"[green]Wrote[/green] {output}")
    logging.getLogger('granule.cli').info("Text video card title=%s output=%s source=%s", title, output, input_path[:60])

if __name__ == "__main__":  # moved to end so all commands are registered when run as a module
    app()
