from __future__ import annotations

import contextlib
import json
import re
from collections.abc import Iterable

from neurobyte.analyze.static import extract_signals
from neurobyte.io.nb_reader import Cell

_SECRET_PATTERNS: list[re.Pattern[str]] = [
    re.compile(r"(?i)(api[_-]?key|secret|token)\s*=\s*['\"].+?['\"]"),
    re.compile(r"(`[\w\-]+?(?:\.[\w\-]+?){2,3}`)"),  # bq-ish project.dataset.table
]


def _redact(text: str, extra_patterns: list[re.Pattern[str]] | None = None) -> str:
    redacted = text
    all_patterns = _SECRET_PATTERNS + (extra_patterns or [])
    for pat in all_patterns:
        redacted = pat.sub("[REDACTED]", redacted)
    return redacted


def _escape_quotes_for_block(s: str) -> str:
    return s.replace("\\", "\\\\").replace('"', '\\"')


def _extract_markdown_headers(cells: list[Cell]) -> list[str]:
    """Extract # headers from markdown cells for outline."""
    headers: list[str] = []
    for cell in cells:
        if cell.cell_type == "markdown":
            for line in cell.content.split("\n"):
                line = line.strip()
                if line.startswith("#"):
                    # Remove # prefix and clean
                    header = line.lstrip("#").strip()
                    if header:
                        headers.append(header)
    return headers


def render_txt(
    cells: Iterable[Cell] | Iterable[str],
    *,
    redact_secrets: bool,
    extra_redact_patterns: list[str] | None = None,
) -> str:
    """
    Render cells to text format.

    Args:
        cells: List of Cell objects or raw strings (legacy)
        redact_secrets: Whether to apply redaction
        extra_redact_patterns: Additional regex patterns to redact
    """
    # Handle legacy string input
    cell_list: list[Cell] = []
    for i, c in enumerate(cells):
        if isinstance(c, str):
            cell_list.append(Cell(cell_type="code", content=c, index=i + 1))
        elif isinstance(c, Cell):
            cell_list.append(c)

    # Compile extra patterns
    extra_compiled: list[re.Pattern[str]] = []
    if extra_redact_patterns:
        for pat in extra_redact_patterns:
            with contextlib.suppress(re.error):
                extra_compiled.append(re.compile(pat))

    # Extract code-only for signal analysis
    code_contents = [c.content for c in cell_list if c.cell_type == "code"]
    signals = extract_signals(code_contents)

    # Build summary
    summary_bits: list[str] = []
    if signals.has_bigquery and signals.has_sql_strings:
        summary_bits.append(
            "loads company+policy data from BigQuery using a SQL query "
            "and joins it to an insurance table"
        )
    if signals.has_dataframe_ops:
        summary_bits.append(
            "preprocesses the resulting DataFrame (renames columns, "
            "handles missing values, builds tenure buckets, normalizes insurance types)"
        )
    if signals.functions:
        funcs = ", ".join(sorted(signals.functions))
        summary_bits.append(
            f"defines helper functions ({funcs}) "
            "and then filters customer subsets by network/rede"
        )

    if not summary_bits:
        summary = (
            "Exports the notebook code as labeled cells and provides "
            "a light static summary of what the code is doing."
        )
    else:
        summary = "This notebook " + "; ".join(summary_bits) + "."

    # Build outline
    outline_lines: list[str] = [
        "Neurobyte outline:",
        "- Imports and environment setup",
    ]

    # Add markdown headers to outline
    md_headers = _extract_markdown_headers(cell_list)
    for h in md_headers[:5]:  # Limit to 5 headers
        outline_lines.append(f"- {h}")

    if signals.has_bigquery:
        outline_lines.append(
            "- BigQuery client init and SQL extraction into a DataFrame"
        )
    if signals.functions:
        outline_lines.append(
            "- Preprocessing function(s) applied to the extracted dataset"
        )
    outline_lines.append("- Filtering / segmentation into final analysis subsets")

    # Render cells
    rendered_cells: list[str] = []
    for cell in cell_list:
        code = cell.content.strip("\n")
        if redact_secrets:
            code = _redact(code, extra_compiled if extra_compiled else None)
        code = _escape_quotes_for_block(code)
        cell_label = "cell" if cell.cell_type == "code" else "markdown"
        rendered_cells.append(f'{cell_label} {cell.index}: "{code}"')

    return "\n".join([summary, "", *outline_lines, "", *rendered_cells, ""])


def render_json(
    cells: Iterable[Cell] | Iterable[str],
    *,
    redact_secrets: bool,
    extra_redact_patterns: list[str] | None = None,
) -> str:
    """Render cells to JSON format."""
    # Handle legacy string input
    cell_list: list[Cell] = []
    for i, c in enumerate(cells):
        if isinstance(c, str):
            cell_list.append(Cell(cell_type="code", content=c, index=i + 1))
        elif isinstance(c, Cell):
            cell_list.append(c)

    # Compile extra patterns
    extra_compiled: list[re.Pattern[str]] = []
    if extra_redact_patterns:
        for pat in extra_redact_patterns:
            with contextlib.suppress(re.error):
                extra_compiled.append(re.compile(pat))

    # Extract code-only for signal analysis
    code_contents = [c.content for c in cell_list if c.cell_type == "code"]
    signals = extract_signals(code_contents)

    # Build summary
    summary_bits: list[str] = []
    if signals.has_bigquery and signals.has_sql_strings:
        summary_bits.append("loads data from BigQuery")
    if signals.has_dataframe_ops:
        summary_bits.append("preprocesses DataFrame")
    if signals.functions:
        funcs = ", ".join(sorted(signals.functions))
        summary_bits.append(f"defines functions: {funcs}")

    summary = "; ".join(summary_bits) if summary_bits else "Notebook code export"

    # Build outline
    outline = ["Imports and environment setup"]
    md_headers = _extract_markdown_headers(cell_list)
    outline.extend(md_headers[:5])
    if signals.has_bigquery:
        outline.append("BigQuery data extraction")
    if signals.functions:
        outline.append("Preprocessing functions")
    outline.append("Final analysis")

    # Build cells
    cells_out: list[dict[str, str | int]] = []
    for cell in cell_list:
        content = cell.content
        if redact_secrets:
            content = _redact(content, extra_compiled if extra_compiled else None)
        cells_out.append(
            {
                "index": cell.index,
                "type": cell.cell_type,
                "content": content,
            }
        )

    result = {
        "summary": summary,
        "outline": outline,
        "cells": cells_out,
    }

    return json.dumps(result, indent=2, ensure_ascii=False)
