"""
SMI Steel Deck Calculator — MCP Server

Wraps the SMI-Backend Julia HTTP API so Claude can run structural
engineering calculations directly in conversation.
"""

import json
import os
from pathlib import Path

import httpx
from mcp.server.fastmcp import FastMCP

BASE_URL = os.environ.get("SMI_BACKEND_URL", "http://159.89.180.137:8080")

mcp = FastMCP(
    "SMI Steel Deck Calculator",
    instructions=(
        "You have access to the SMI steel composite deck calculation engine. "
        "Use get_options first to see valid parameter values, then calculate_steel_deck "
        "to run an analysis. Results include bare deck section properties, shear strength, "
        "and web crippling capacity. Use export_pdf to generate a formal design report."
    ),
)


def _client() -> httpx.Client:
    return httpx.Client(base_url=BASE_URL, timeout=30.0)


# ── Tool: health_check ────────────────────────────────────────────────────────

@mcp.tool()
def health_check() -> str:
    """Check whether the SMI backend is running and reachable."""
    try:
        with _client() as c:
            r = c.get("/")
            r.raise_for_status()
            data = r.json()
            return f"Backend is online. Available endpoints: {', '.join(data.get('endpoints', []))}"
    except httpx.ConnectError:
        return f"Cannot reach backend at {BASE_URL}. Make sure the Docker container is running."
    except Exception as e:
        return f"Error: {e}"


# ── Tool: get_options ─────────────────────────────────────────────────────────

@mcp.tool()
def get_options() -> str:
    """
    Return all valid parameter values for the calculate_steel_deck tool.

    Includes: deck profiles, thicknesses (mm), steel grades, design methods
    (ASD / LRFD), and concrete grades.
    """
    with _client() as c:
        r = c.get("/options")
        r.raise_for_status()
        data = r.json()

    lines = [
        "Available calculation parameters:",
        f"  profiles        : {', '.join(data['profiles'])}",
        f"  thicknesses_mm  : {', '.join(str(t) for t in data['thicknesses_mm'])}",
        f"  grades          : {', '.join(data['grades'])}",
        f"  design_methods  : {', '.join(data['design_methods'])}",
        f"  concrete_grades : {', '.join(data['concrete_grades'])}",
    ]
    return "\n".join(lines)


# ── Tool: calculate_steel_deck ────────────────────────────────────────────────

@mcp.tool()
def calculate_steel_deck(
    profile: str = "WH-38-152",
    thickness_mm: str = "1.00",
    grade: str = "ASTM A653 SS Grade 50/1",
    design_method: str = "ASD",
    support_width_mm: str = "150",
    concrete_grade: str = "C30",
) -> str:
    """
    Run a full structural analysis for a composite steel deck configuration.

    Args:
        profile: Deck profile name. One of: WH-38-152, 2WH-36, 3WH-36.
        thickness_mm: Sheet thickness in mm. One of: 0.71, 0.80, 0.90, 1.00, 1.20, 1.50.
        grade: Steel grade. E.g. "ASTM A653 SS Grade 50/1", "AS 1397 :2001 Grade 450".
        design_method: "ASD" (Allowable Strength) or "LRFD" (Load and Resistance Factor).
        support_width_mm: Bearing / support width in mm. Typical values: 50–200.
        concrete_grade: Concrete compressive strength grade. E.g. "C30", "C25".

    Returns:
        A structured summary of:
        - Bare deck section properties (area, moment of inertia, section modulus, etc.)
        - Shear strength results for the selected profile / thickness
        - Web crippling capacity for the selected profile / thickness
    """
    payload = {
        "profile": profile,
        "thickness": thickness_mm,
        "grade": grade,
        "designMethod": design_method,
        "supportWidth": support_width_mm,
        "concreteGrade": concrete_grade,
    }

    with _client() as c:
        r = c.post("/calculate", json=payload)

    if r.status_code != 200:
        err = r.json().get("error", r.text)
        return f"Calculation failed ({r.status_code}): {err}"

    data = r.json()
    return _format_calculate_result(data, profile, thickness_mm, design_method)


def _format_calculate_result(data: dict, profile: str, thickness_mm: str, design_method: str) -> str:
    lines = [
        f"=== SMI Calculation Results ===",
        f"Profile: {profile}  |  Thickness: {thickness_mm} mm  |  Method: {design_method}",
        "",
    ]

    # ── Bare shear ────────────────────────────────────────────────────────────
    shear = data.get("bare_shear", {})
    if shear:
        lines.append("── Bare Deck Shear Strength ──")
        for span_case, entries in shear.items():
            lines.append(f"  Span condition: {span_case}")
            if isinstance(entries, list):
                for entry in entries:
                    _append_entry(lines, entry)
            else:
                _append_entry(lines, entries)
        lines.append("")

    # ── Web crippling ─────────────────────────────────────────────────────────
    wc = data.get("web_crippling", {})
    if wc:
        lines.append("── Web Crippling Capacity ──")
        for load_case, entries in wc.items():
            lines.append(f"  Load case: {load_case}")
            if isinstance(entries, list):
                for entry in entries:
                    _append_entry(lines, entry)
            else:
                _append_entry(lines, entries)
        lines.append("")

    # ── Bare deck section properties (summary for selected profile) ───────────
    bdp = data.get("bare_deck_properties", {})
    profile_key = _profile_key(profile)
    if bdp and profile_key in bdp:
        lines.append("── Section Properties (selected profile, all thicknesses) ──")
        for entry in bdp[profile_key]:
            sp = entry.get("section_properties", entry)
            t  = sp.get("t_mm") or entry.get("t_mm", "?")
            lines.append(f"  t = {t} mm:")
            for k, v in sp.items():
                if k in ("t_mm", "node_geometry"):
                    continue
                if isinstance(v, float):
                    lines.append(f"    {k}: {v:.4g}")
                elif not isinstance(v, (list, dict)):
                    lines.append(f"    {k}: {v}")
        lines.append("")

    lines.append("(Full JSON available on request)")
    return "\n".join(lines)


def _append_entry(lines: list[str], entry: dict) -> None:
    for k, v in entry.items():
        if isinstance(v, float):
            lines.append(f"    {k}: {v:.4g}")
        elif not isinstance(v, (list, dict)):
            lines.append(f"    {k}: {v}")


def _profile_key(profile_frontend: str) -> str:
    mapping = {"WH-38-152": "WH", "2WH-36": "2WH", "3WH-36": "3WH"}
    return mapping.get(profile_frontend, profile_frontend)


# ── Tool: export_pdf ──────────────────────────────────────────────────────────

@mcp.tool()
def export_pdf(
    profile: str = "WH-38-152",
    thickness_mm: str = "1.00",
    grade: str = "ASTM A653 SS Grade 50/1",
    design_method: str = "ASD",
    support_width_mm: str = "150",
    concrete_grade: str = "C30",
    save_path: str = "",
) -> str:
    """
    Generate a formal PDF design report and save it to disk.

    Uses the same parameters as calculate_steel_deck.

    Args:
        profile: Deck profile name.
        thickness_mm: Sheet thickness in mm.
        grade: Steel grade.
        design_method: "ASD" or "LRFD".
        support_width_mm: Bearing width in mm.
        concrete_grade: Concrete grade (e.g. "C30").
        save_path: Absolute path where the PDF should be saved.
                   Defaults to ~/Downloads/SMI_Report_<profile>_<thickness>mm.pdf

    Returns:
        The path where the PDF was saved.
    """
    payload = {
        "profile": profile,
        "thickness": thickness_mm,
        "grade": grade,
        "designMethod": design_method,
        "supportWidth": support_width_mm,
        "concreteGrade": concrete_grade,
    }

    with _client() as c:
        r = c.post("/export-pdf", json=payload)

    if r.status_code != 200:
        try:
            err = r.json().get("error", r.text)
        except Exception:
            err = r.text
        return f"PDF export failed ({r.status_code}): {err}"

    if not save_path:
        downloads = Path.home() / "Downloads"
        safe_profile = profile.replace("-", "_")
        safe_t = thickness_mm.replace(".", "p")
        save_path = str(downloads / f"SMI_Report_{safe_profile}_{safe_t}mm.pdf")

    Path(save_path).parent.mkdir(parents=True, exist_ok=True)
    Path(save_path).write_bytes(r.content)

    return f"PDF saved to: {save_path}  ({len(r.content) // 1024} KB)"


# ── Entry point ───────────────────────────────────────────────────────────────

if __name__ == "__main__":
    mcp.run()
