"""DICOM inversion time (TI) analysis tool for MOLLI sequences."""

import asyncio
import json
import statistics
from collections import defaultdict
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

import pydicom
from mcp.types import ToolAnnotations

from dicom_mcp.server import mcp
from dicom_mcp.constants import ResponseFormat
from dicom_mcp.helpers.tags import _safe_get_tag, _format_markdown_table
from dicom_mcp.helpers.sequence import _identify_sequence_type
from dicom_mcp.helpers.files import _find_dicom_files
from dicom_mcp.helpers.philips import _resolve_philips_private_tag


def _extract_inversion_time(ds: pydicom.Dataset) -> Optional[float]:
    """Extract inversion time from a DICOM dataset, handling vendor differences.

    Fallback chain:
      1. Standard tag (0018,0082) InversionTime — works for Siemens/GE
      2. Philips private tag DD 006, offset 0x72 in group 2005 — confirmed
         at (2005,1572) on Philips Achieva dStream
      3. Philips private tag DD 001, offset 0x20 in group 2001 — alternate
         location on some Philips software versions

    Returns the TI value in milliseconds, or None if not found.
    """
    # 1. Standard InversionTime tag
    ti_val = _safe_get_tag(ds, (0x0018, 0x0082))
    if ti_val != "N/A":
        try:
            return float(ti_val)
        except (ValueError, TypeError):
            pass

    # 2-3. Philips private tag fallbacks
    manufacturer = _safe_get_tag(ds, (0x0008, 0x0070))
    if "philips" not in str(manufacturer).lower():
        return None

    # Try DD 006, offset 0x72 in group 2005
    resolved_tag, _, value_str = _resolve_philips_private_tag(
        ds, dd_number=6, element_offset=0x72, private_group=0x2005
    )
    if resolved_tag is not None and value_str is not None:
        try:
            return float(value_str)
        except (ValueError, TypeError):
            pass

    # Try DD 001, offset 0x20 in group 2001
    resolved_tag, _, value_str = _resolve_philips_private_tag(
        ds, dd_number=1, element_offset=0x20, private_group=0x2001
    )
    if resolved_tag is not None and value_str is not None:
        try:
            return float(value_str)
        except (ValueError, TypeError):
            pass

    return None


@mcp.tool(
    name="dicom_analyze_ti",
    annotations=ToolAnnotations(
        title="Analyze Inversion Times (MOLLI)",
        readOnlyHint=True,
        destructiveHint=False,
        idempotentHint=True,
        openWorldHint=False,
    ),
)
async def dicom_analyze_ti(
    directory: str,
    recursive: bool = True,
    gap_threshold: float = 2500.0,
    response_format: ResponseFormat = ResponseFormat.MARKDOWN,
) -> str:
    """Analyze inversion times from MOLLI/T1-mapping sequences.

    Extracts TI values across all T1 mapping files in a directory, handling
    manufacturer-specific differences (Siemens/GE use standard tags, Philips
    stores TI in private tags). Groups by series, sorts by instance number,
    and computes statistics including gap analysis.

    Automatically handles:
      - Siemens/GE: standard InversionTime tag (0018,0082)
      - Philips: private tag (2005,xx72) via DD 006 block resolution

    Args:
        directory: Directory containing MOLLI/T1 mapping DICOM files
        recursive: Search subdirectories (default True)
        gap_threshold: Flag gaps between consecutive TIs exceeding this
                       value in ms (default 2500.0)
        response_format: Output format (markdown or json)
    """
    try:
        dir_path = Path(directory)
        if not dir_path.exists():
            return f"Error: Directory not found: {directory}"

        all_files, truncated = await asyncio.to_thread(
            _find_dicom_files, dir_path, recursive
        )

        if not all_files:
            return f"No DICOM files found in {directory}"

        # Filter to T1 mapping / MOLLI files
        ti_files: List[Tuple[Path, pydicom.Dataset]] = []
        for fp, ds in all_files:
            seq_type = _identify_sequence_type(ds)
            if seq_type.value == "t1_mapping":
                ti_files.append((fp, ds))
                continue
            # Also catch files with MOLLI in the description that might
            # not be detected as t1_mapping by the heuristic
            desc = str(_safe_get_tag(ds, (0x0008, 0x103E))).upper()
            if "MOLLI" in desc:
                ti_files.append((fp, ds))

        if not ti_files:
            return (
                f"No T1 mapping or MOLLI sequences found in {directory}. "
                f"Scanned {len(all_files)} DICOM files."
            )

        # Group by (SeriesNumber, SeriesDescription) and extract TI
        series_data: Dict[Tuple[str, str], List[Dict[str, Any]]] = defaultdict(list)

        for fp, ds in ti_files:
            series_num = str(_safe_get_tag(ds, (0x0020, 0x0011)))
            series_desc = str(_safe_get_tag(ds, (0x0008, 0x103E)))
            instance_num = _safe_get_tag(ds, (0x0020, 0x0013))
            manufacturer = str(_safe_get_tag(ds, (0x0008, 0x0070)))
            ti_value = _extract_inversion_time(ds)

            try:
                inst_num = int(instance_num)
            except (ValueError, TypeError):
                inst_num = 0

            series_data[(series_num, series_desc)].append(
                {
                    "file": fp.name,
                    "instance_number": inst_num,
                    "inversion_time": ti_value,
                    "manufacturer": manufacturer,
                }
            )

        # Analyze each series
        series_results: List[Dict[str, Any]] = []

        for (series_num, series_desc), files in sorted(series_data.items()):
            # Sort by instance number
            files.sort(key=lambda x: x["instance_number"])

            all_tis = [f["inversion_time"] for f in files]
            non_zero_tis = [ti for ti in all_tis if ti is not None and ti > 0]
            zero_tis = [ti for ti in all_tis if ti is not None and ti == 0]
            none_tis = [ti for ti in all_tis if ti is None]

            # Compute statistics on non-zero TIs
            stats: Dict[str, Any] = {
                "total_files": len(files),
                "ti_found": len(all_tis) - len(none_tis),
                "ti_not_found": len(none_tis),
                "zero_ti_count": len(zero_tis),
                "non_zero_ti_count": len(non_zero_tis),
            }

            if non_zero_tis:
                sorted_tis = sorted(non_zero_tis)
                stats["ti_min"] = min(non_zero_tis)
                stats["ti_max"] = max(non_zero_tis)
                stats["ti_range"] = max(non_zero_tis) - min(non_zero_tis)
                stats["ti_mean"] = statistics.mean(non_zero_tis)
                stats["ti_median"] = statistics.median(non_zero_tis)

                # Compute consecutive gaps (based on sorted order)
                gaps = [
                    sorted_tis[i + 1] - sorted_tis[i]
                    for i in range(len(sorted_tis) - 1)
                ]
                if gaps:
                    stats["max_gap"] = max(gaps)
                    stats["gap_threshold_exceeded"] = max(gaps) > gap_threshold
                    stats["gaps"] = gaps
                else:
                    stats["max_gap"] = 0.0
                    stats["gap_threshold_exceeded"] = False
                    stats["gaps"] = []

            manufacturer = files[0]["manufacturer"] if files else "Unknown"

            ordered_tis = [
                {
                    "instance": f["instance_number"],
                    "ti": f["inversion_time"],
                    "file": f["file"],
                }
                for f in files
            ]

            series_results.append(
                {
                    "series_number": series_num,
                    "series_description": series_desc,
                    "manufacturer": manufacturer,
                    "statistics": stats,
                    "ordered_inversion_times": ordered_tis,
                }
            )

        if response_format == ResponseFormat.JSON:
            output = {
                "directory": str(dir_path),
                "total_files_scanned": len(all_files),
                "truncated": truncated,
                "t1_mapping_files": len(ti_files),
                "series_count": len(series_results),
                "gap_threshold": gap_threshold,
                "series": series_results,
            }
            return json.dumps(output, indent=2)
        else:
            output_lines = [
                f"# Inversion Time Analysis: {dir_path.name}\n",
                f"**Files scanned**: {len(all_files)}",
                f"**T1 mapping files**: {len(ti_files)}",
                f"**Series found**: {len(series_results)}",
                f"**Gap threshold**: {gap_threshold} ms",
            ]

            if truncated:
                output_lines.append(
                    "\n**Warning**: File scan was truncated. "
                    "Results may be incomplete."
                )

            for sr in series_results:
                stats = sr["statistics"]
                output_lines.append(
                    f"\n## Series {sr['series_number']}: "
                    f"{sr['series_description']}\n"
                )
                output_lines.append(f"**Manufacturer**: {sr['manufacturer']}")
                output_lines.append(f"**Total files**: {stats['total_files']}")

                if stats["ti_not_found"] > 0:
                    output_lines.append(
                        f"**TI not found**: {stats['ti_not_found']} files "
                        "(no standard or private TI tag)"
                    )

                output_lines.append(f"**Non-zero TIs**: {stats['non_zero_ti_count']}")
                output_lines.append(
                    f"**Zero TIs**: {stats['zero_ti_count']} " "(likely output maps)"
                )

                if stats.get("ti_min") is not None:
                    output_lines.append(
                        f"\n**TI range**: {stats['ti_min']:.1f} - "
                        f"{stats['ti_max']:.1f} ms "
                        f"(range: {stats['ti_range']:.1f} ms)"
                    )
                    output_lines.append(
                        f"**Mean**: {stats['ti_mean']:.1f} ms | "
                        f"**Median**: {stats['ti_median']:.1f} ms"
                    )

                    if stats.get("max_gap", 0) > 0:
                        gap_warning = (
                            " **WARNING: exceeds threshold**"
                            if stats.get("gap_threshold_exceeded")
                            else ""
                        )
                        output_lines.append(
                            f"**Largest gap**: {stats['max_gap']:.1f} ms"
                            f"{gap_warning}"
                        )

                # Ordered TI table
                output_lines.append("\n### Inversion Times (ordered)\n")
                headers = ["Instance", "TI (ms)", "File"]
                rows = []
                for ti_entry in sr["ordered_inversion_times"]:
                    ti_display = (
                        f"{ti_entry['ti']:.1f}" if ti_entry["ti"] is not None else "N/A"
                    )
                    rows.append(
                        [
                            str(ti_entry["instance"]),
                            ti_display,
                            ti_entry["file"],
                        ]
                    )
                output_lines.append(_format_markdown_table(headers, rows))

            return "\n".join(output_lines)

    except Exception as e:
        return f"Error analyzing inversion times: {str(e)}"
