"""
CLI entry point for pbi-semantic-doc.

Usage:
    pbi-semantic-doc <model_path> [--output <path>] [--format md]

Examples:
    pbi-semantic-doc ./MyReport.SemanticModel
    pbi-semantic-doc ./MyReport.SemanticModel --output ./docs/MODEL.md
    pbi-semantic-doc . --output DOC.md
    pbi-semantic-doc ./MyProject --combined --output ./docs/FULL.md
"""

from __future__ import annotations

import argparse
import json
import re
import sys
from datetime import date
from pathlib import Path

from .parser import TmdlParser
from .generator import MarkdownGenerator
from .report_parser import ReportParser
from .report_generator import ReportGenerator


def _safe_name(name: str) -> str:
    """Convert a model/report name to a filesystem-safe string."""
    safe = re.sub(r'[<>:"/\\|?*\x00-\x1f]', "", name)
    safe = re.sub(r"\s+", "_", safe.strip())
    return safe or "output"


def _strip_h1(content: str) -> str:
    """Remove the first top-level heading from a Markdown document.

    Used when assembling a combined document so each sub-document's h1
    becomes an h2 section heading in the unified output.
    """
    lines = content.splitlines()
    if lines and lines[0].startswith("# "):
        lines = lines[1:]
    # Drop leading blank lines left after the removed h1
    while lines and not lines[0].strip():
        lines = lines[1:]
    return "\n".join(lines)


def _combined_markdown(
    model,
    report_metrics,
    project_name: str,
) -> str:
    """Build a single unified Markdown document with both model and report sections."""
    today = date.today().strftime("%Y-%m-%d")

    # ── top-level header ──────────────────────────────────────────────────
    header_lines = [
        f"# DOC — {project_name}",
        "",
        f"> Combined Power BI project documentation &middot; Generated {today}",
        "",
        "---",
        "",
        "## Contents",
        "",
    ]
    if model:
        tables = len(model.visible_tables)
        measures = sum(len(t.measures) for t in model.visible_tables)
        rels = len(model.relationships)
        header_lines.append(
            f"- [Semantic Model](#semantic-model)"
            f" — {tables} table{'s' if tables != 1 else ''},"
            f" {measures} measure{'s' if measures != 1 else ''},"
            f" {rels} relationship{'s' if rels != 1 else ''}"
        )
    if report_metrics:
        pages = report_metrics.total_pages
        visuals = report_metrics.total_visuals
        header_lines.append(
            f"- [Report](#report)"
            f" — {pages} page{'s' if pages != 1 else ''},"
            f" {visuals} visual{'s' if visuals != 1 else ''}"
        )
    header_lines.append("")

    parts: list[str] = ["\n".join(header_lines)]

    # ── semantic model section ────────────────────────────────────────────
    if model:
        gen = MarkdownGenerator()
        body = _strip_h1(gen.generate(model))
        parts.append(f"## Semantic Model\n\n{body}")

    # ── report section ────────────────────────────────────────────────────
    if report_metrics:
        gen = ReportGenerator()
        body = _strip_h1(gen.generate_markdown(report_metrics))
        parts.append(f"## Report\n\n{body}")

    return "\n\n---\n\n".join(parts)


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        prog="pbi-semantic-doc",
        description="Generate documentation from a Power BI semantic model (TMDL format) or analyze report structure.",
    )
    p.add_argument(
        "model_path",
        metavar="PATH",
        help=(
            "Path to analyze. Accepts:\n"
            "  - a *.SemanticModel directory (for model documentation)\n"
            "  - a *.Report directory (for report analysis with --analyze-report)\n"
            "  - a .pbip project folder (for combined analysis with --combined)\n"
            "  - a parent folder containing the above"
        ),
    )
    p.add_argument(
        "--analyze-report",
        action="store_true",
        help="Analyze report structure instead of semantic model",
    )
    p.add_argument(
        "--combined",
        action="store_true",
        help="Analyze both semantic model and report (for .pbip projects)",
    )
    p.add_argument(
        "--output", "-o",
        metavar="OUTPUT_PATH",
        default=None,
        help=(
            "Where to write the output file. "
            "Defaults to DOC_<name>.md in the parent of the input folder."
        ),
    )
    p.add_argument(
        "--format", "-f",
        choices=["md", "json", "text"],
        default="md",
        help="Output format. Default: md",
    )
    p.add_argument(
        "--quiet", "-q",
        action="store_true",
        help="Suppress informational output.",
    )
    return p


def main(argv: list[str] | None = None) -> int:
    # Ensure stdout can handle Unicode on Windows (cp1252 terminals)
    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8", errors="replace")

    args = build_parser().parse_args(argv)

    if args.combined:
        return analyze_combined(args)
    elif args.analyze_report:
        return analyze_report(args)
    else:
        return analyze_semantic_model(args)


def analyze_semantic_model(args) -> int:
    """Analizza solo il modello semantico."""
    model_path = Path(args.model_path).resolve()
    if not model_path.exists():
        print(f"Error: path does not exist: {model_path}", file=sys.stderr)
        return 1

    # Parse first so we can use the model name in the default output filename
    try:
        parser = TmdlParser()
        model = parser.parse(model_path)
    except FileNotFoundError as exc:
        print(f"Error: {exc}", file=sys.stderr)
        return 1
    except Exception as exc:  # noqa: BLE001
        print(f"Unexpected error while parsing: {exc}", file=sys.stderr)
        return 1

    # Determine output path — place NEXT TO (not inside) the .SemanticModel folder
    if args.output:
        output_path = Path(args.output).resolve()
    else:
        output_path = model_path.parent / f"DOC_{_safe_name(model.name)}.md"

    output_path.parent.mkdir(parents=True, exist_ok=True)

    generator = MarkdownGenerator()
    content = generator.generate(model)
    output_path.write_text(content, encoding="utf-8")

    if not args.quiet:
        tables = len(model.visible_tables)
        measures = sum(len(t.measures) for t in model.visible_tables)
        relationships = len(model.relationships)
        print(
            f"✓ Documentation generated: {output_path}\n"
            f"  Model   : {model.name}\n"
            f"  Tables  : {tables}\n"
            f"  Measures: {measures}\n"
            f"  Rels    : {relationships}"
        )

    return 0


def analyze_report(args) -> int:
    """Analizza solo il report."""
    report_path = Path(args.model_path).resolve()
    if not report_path.exists():
        print(f"Error: path does not exist: {report_path}", file=sys.stderr)
        return 1

    try:
        parser = ReportParser()
        report = parser.parse(report_path)
        metrics = report.calculate_metrics()
    except FileNotFoundError as exc:
        print(f"Error: {exc}", file=sys.stderr)
        return 1
    except Exception as exc:  # noqa: BLE001
        print(f"Unexpected error while parsing: {exc}", file=sys.stderr)
        return 1

    # Determine output path — place NEXT TO (not inside) the .Report folder
    if args.output:
        output_path = Path(args.output).resolve()
    else:
        safe = _safe_name(metrics.report_name)
        if args.format == "json":
            output_path = report_path.parent / f"DOC_{safe}.json"
        elif args.format == "text":
            output_path = None  # print to console
        else:
            output_path = report_path.parent / f"DOC_{safe}.md"

    if output_path:
        output_path.parent.mkdir(parents=True, exist_ok=True)

    generator = ReportGenerator()

    if args.format == "json":
        content = generator.generate_json(metrics)
    elif args.format == "text":
        content = generator.generate_text(metrics)
    else:
        content = generator.generate_markdown(metrics)

    if output_path:
        output_path.write_text(content, encoding="utf-8")
        if not args.quiet:
            print(
                f"✓ Report analysis saved: {output_path}\n"
                f"  Report  : {metrics.report_name}\n"
                f"  Format  : {metrics.report_format}\n"
                f"  Pages   : {metrics.total_pages}\n"
                f"  Visuals : {metrics.total_visuals}\n"
                f"  Complexity: {metrics.complexity_index:.0%}"
            )
    else:
        print(content)

    return 0


def analyze_combined(args) -> int:
    """Analizza sia modello semantico che report, producendo un unico documento."""
    project_path = Path(args.model_path).resolve()
    if not project_path.exists():
        print(f"Error: path does not exist: {project_path}", file=sys.stderr)
        return 1

    # Discover .SemanticModel and .Report subfolders
    semantic_model_path = None
    report_path = None

    for child in project_path.iterdir():
        if child.is_dir():
            if ".SemanticModel" in child.name:
                semantic_model_path = child
            elif ".Report" in child.name:
                report_path = child

    if not semantic_model_path and not report_path:
        print(
            f"Error: No .SemanticModel or .Report folders found in: {project_path}\n"
            f"Expected a .pbip project structure",
            file=sys.stderr,
        )
        return 1

    # Parse both components first so we can use the real model/report name
    model = None
    report_metrics = None

    if semantic_model_path:
        try:
            tmdl_parser = TmdlParser()
            model = tmdl_parser.parse(semantic_model_path)
        except Exception as exc:
            print(f"Warning: Failed to parse semantic model: {exc}", file=sys.stderr)

    if report_path:
        try:
            rpt_parser = ReportParser()
            report = rpt_parser.parse(report_path)
            report_metrics = report.calculate_metrics()
        except Exception as exc:
            print(f"Warning: Failed to parse report: {exc}", file=sys.stderr)

    # Determine output path — use real model/report name, not the folder name
    if args.output:
        output_path = Path(args.output).resolve()
    else:
        project_name = (
            model.name if model
            else (report_metrics.report_name if report_metrics else project_path.name)
        )
        ext = ".json" if args.format == "json" else ".md"
        output_path = project_path / f"DOC_{_safe_name(project_name)}{ext}"

    output_path.parent.mkdir(parents=True, exist_ok=True)

    # Generate unified output
    if args.format == "json":
        combined: dict = {}
        if model:
            combined["semantic_model"] = {
                "name": model.name,
                "tables": len(model.visible_tables),
                "measures": sum(len(t.measures) for t in model.visible_tables),
                "relationships": len(model.relationships),
            }
        if report_metrics:
            combined["report"] = report_metrics.to_dict()
        content = json.dumps(combined, indent=2, ensure_ascii=False)
    else:  # markdown — unified single document
        project_name = (
            model.name if model
            else (report_metrics.report_name if report_metrics else project_path.name)
        )
        content = _combined_markdown(model, report_metrics, project_name)

    output_path.write_text(content, encoding="utf-8")

    if not args.quiet:
        print(f"✓ Combined documentation saved: {output_path}")
        if model:
            print(f"  Model : {model.name} ({len(model.visible_tables)} tables, "
                  f"{sum(len(t.measures) for t in model.visible_tables)} measures)")
        if report_metrics:
            print(f"  Report: {report_metrics.report_name} "
                  f"({report_metrics.total_pages} pages, {report_metrics.total_visuals} visuals)")

    return 0


if __name__ == "__main__":
    sys.exit(main())
