"""DICOM metadata extraction and comparison tools."""

import asyncio
import json
from pathlib import Path
from typing import Dict, List, Optional

import pydicom
from pydicom import datadict
from pydicom.errors import InvalidDicomError
from mcp.types import ToolAnnotations

from dicom_mcp.server import mcp
from dicom_mcp.constants import COMMON_TAGS, VALID_TAG_GROUPS, ResponseFormat
from dicom_mcp.helpers.tags import (
    _safe_get_tag,
    _validate_tag_groups,
    _format_markdown_table,
)
from dicom_mcp.helpers.philips import _resolve_philips_private_tag
from dicom_mcp.pii import redact_if_pii


@mcp.tool(
    name="dicom_get_metadata",
    annotations=ToolAnnotations(
        title="Get DICOM File Metadata",
        readOnlyHint=True,
        destructiveHint=False,
        idempotentHint=True,
        openWorldHint=False,
    ),
)
async def dicom_get_metadata(
    file_path: str,
    tag_groups: Optional[List[str]] = None,
    custom_tags: Optional[List[str]] = None,
    philips_private_tags: Optional[List[Dict[str, int]]] = None,
    response_format: ResponseFormat = ResponseFormat.MARKDOWN,
) -> str:
    """Extract metadata from a DICOM file.

    Reads DICOM headers and extracts commonly used tags organized by
    category. Supports both predefined tag groups and custom tag specification.

    Available tag groups: patient_info, study_info, series_info, image_info,
    acquisition, manufacturer, equipment, geometry, pixel_data.

    For Philips files, you can also resolve private tags by providing
    philips_private_tags — a list of dicts with keys "dd_number" and
    "element_offset" (and optionally "private_group", default 0x2005).
    Example: [{"dd_number": 1, "element_offset": 133}] resolves the
    DD 001 block's element at offset 0x85.
    """
    try:
        fp = Path(file_path)
        if not fp.exists():
            return f"Error: File not found: {file_path}"

        ds = await asyncio.to_thread(pydicom.dcmread, fp, stop_before_pixels=True)

        if tag_groups:
            invalid = _validate_tag_groups(tag_groups)
            if invalid:
                return (
                    f"Error: Unknown tag group(s): {', '.join(invalid)}. "
                    f"Available groups: {', '.join(VALID_TAG_GROUPS)}"
                )
            tag_groups_to_use = {
                k: v for k, v in COMMON_TAGS.items() if k in tag_groups
            }
        else:
            tag_groups_to_use = COMMON_TAGS

        metadata = {}
        for group_name, tags in tag_groups_to_use.items():
            group_data = {}
            for tag in tags:
                tag_name = datadict.keyword_for_tag(tag)
                value = _safe_get_tag(ds, tag)
                group_data[tag_name] = redact_if_pii(tag, value)
            metadata[group_name] = group_data

        if custom_tags:
            custom_data = {}
            for tag_str in custom_tags:
                try:
                    group, element = tag_str.split(",")
                    tag = (int(group, 16), int(element, 16))
                    tag_name = datadict.keyword_for_tag(tag) or f"Tag_{tag_str}"
                    value = _safe_get_tag(ds, tag)
                    custom_data[tag_name] = value
                except Exception as e:
                    custom_data[f"Error_{tag_str}"] = f"Invalid tag: {str(e)}"
            metadata["custom_tags"] = custom_data

        if philips_private_tags:
            private_data = {}
            for entry in philips_private_tags:
                dd_num = entry.get("dd_number")
                offset = entry.get("element_offset")
                group = entry.get("private_group", 0x2005)

                if dd_num is None or offset is None:
                    private_data[
                        (
                            f"Error_DD{dd_num}_0x{offset:02X}"
                            if offset
                            else "Error_invalid"
                        )
                    ] = "Both dd_number and element_offset are required"
                    continue

                resolved_tag, creator_str, value_str = _resolve_philips_private_tag(
                    ds, dd_num, offset, group
                )

                label = f"DD{dd_num:03d}_0x{offset:02X}"
                if resolved_tag is not None:
                    tag_hex = f"({resolved_tag[0]:04X},{resolved_tag[1]:04X})"
                    private_data[label] = {
                        "resolved_tag": tag_hex,
                        "creator": creator_str,
                        "value": value_str if value_str is not None else "N/A",
                    }
                else:
                    private_data[label] = {
                        "resolved_tag": None,
                        "error": f"No Private Creator found for DD {dd_num:03d} in group ({group:04X})",
                    }
            metadata["philips_private"] = private_data

        if response_format == ResponseFormat.JSON:
            result = {"file_path": str(fp), "metadata": metadata}
            return json.dumps(result, indent=2)
        else:
            output = [f"# DICOM Metadata: {fp.name}\n"]
            for group_name, group_data in metadata.items():
                output.append(f"## {group_name.replace('_', ' ').title()}\n")
                if group_name == "philips_private":
                    for label, data in group_data.items():
                        if isinstance(data, dict) and "error" in data:
                            output.append(f"- **{label}**: {data['error']}")
                        elif isinstance(data, dict):
                            output.append(
                                f"- **{label}** [{data['resolved_tag']}]: "
                                f"{data['value']}  *(creator: {data['creator']})*"
                            )
                        else:
                            output.append(f"- **{label}**: {data}")
                else:
                    for tag_name, value in group_data.items():
                        output.append(f"- **{tag_name}**: {value}")
                output.append("")
            return "\n".join(output)

    except InvalidDicomError:
        return f"Error: Not a valid DICOM file: {file_path}"
    except Exception as e:
        return f"Error reading DICOM metadata: {str(e)}"


@mcp.tool(
    name="dicom_compare_headers",
    annotations=ToolAnnotations(
        title="Compare DICOM Headers",
        readOnlyHint=True,
        destructiveHint=False,
        idempotentHint=True,
        openWorldHint=False,
    ),
)
async def dicom_compare_headers(
    file_paths: List[str],
    tag_groups: Optional[List[str]] = None,
    show_differences_only: bool = False,
    response_format: ResponseFormat = ResponseFormat.MARKDOWN,
) -> str:
    """Compare DICOM headers across multiple files.

    Compares specified DICOM tags across 2-10 files, highlighting
    differences. Useful for validating sequence consistency and
    identifying which images were selected from Dixon sequences.

    Available tag groups: patient_info, study_info, series_info, image_info,
    acquisition, manufacturer, equipment, geometry, pixel_data.
    """
    try:
        if len(file_paths) < 2 or len(file_paths) > 10:
            return "Error: Provide between 2 and 10 file paths to compare."

        paths = [Path(p) for p in file_paths]
        for fp in paths:
            if not fp.exists():
                return f"Error: File not found: {fp}"

        def _read_all():
            datasets = []
            for fp in paths:
                ds = pydicom.dcmread(fp, stop_before_pixels=True)
                datasets.append((fp, ds))
            return datasets

        datasets = await asyncio.to_thread(_read_all)

        effective_groups = tag_groups if tag_groups else ["acquisition", "series_info"]
        invalid = _validate_tag_groups(effective_groups)
        if invalid:
            return (
                f"Error: Unknown tag group(s): {', '.join(invalid)}. "
                f"Available groups: {', '.join(VALID_TAG_GROUPS)}"
            )

        tags_to_compare = []
        for group in effective_groups:
            tags_to_compare.extend(COMMON_TAGS[group])

        comparison = {}
        for tag in tags_to_compare:
            tag_name = datadict.keyword_for_tag(tag)
            values = [redact_if_pii(tag, _safe_get_tag(ds, tag)) for _, ds in datasets]
            consistent = len(set(values)) == 1
            if show_differences_only and consistent:
                continue
            comparison[tag_name] = {"values": values, "consistent": consistent}

        if response_format == ResponseFormat.JSON:
            result = {
                "files": [str(fp) for fp, _ in datasets],
                "comparison": comparison,
            }
            return json.dumps(result, indent=2)
        else:
            output = [
                "# DICOM Header Comparison\n",
                f"Comparing {len(datasets)} files:\n",
            ]
            for i, (fp, _) in enumerate(datasets, 1):
                output.append(f"{i}. {fp.name}")
            output.append("")

            if comparison:
                headers = (
                    ["Tag"] + [f"File {i+1}" for i in range(len(datasets))] + ["Status"]
                )
                rows = []
                for tag_name, data in comparison.items():
                    status = "Consistent" if data["consistent"] else "Different"
                    row = [tag_name] + data["values"] + [status]
                    rows.append(row)
                output.append(_format_markdown_table(headers, rows))
            else:
                if show_differences_only:
                    output.append("All compared tags are consistent across files.")
                else:
                    output.append("No tags to compare.")
            return "\n".join(output)

    except Exception as e:
        return f"Error comparing DICOM headers: {str(e)}"
