"""DICOM file search tool with filter parsing."""

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

import pydicom
from mcp.types import ToolAnnotations

from dicom_mcp.server import mcp
from dicom_mcp.config import MAX_FILES
from dicom_mcp.constants import ResponseFormat
from dicom_mcp.helpers.tags import _safe_get_tag, _resolve_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.filters import _parse_filter, _apply_filter


@mcp.tool(
    name="dicom_search",
    annotations=ToolAnnotations(
        title="Search DICOM Files by Criteria",
        readOnlyHint=True,
        destructiveHint=False,
        idempotentHint=True,
        openWorldHint=False,
    ),
)
async def dicom_search(
    directory: str,
    filters: List[str],
    recursive: bool = True,
    mode: str = "summary",
    response_format: ResponseFormat = ResponseFormat.MARKDOWN,
) -> str:
    """Search for DICOM files matching specific criteria.

    Finds files where tag values match all given filter conditions (AND logic).
    Returns matching file paths, a count, or a summary depending on mode.

    Filter syntax (one filter per list element):

      Text operators (case-insensitive):
        "SeriesDescription contains MOST"
        "SeriesDescription is LMS MOST"
        "PatientName is not UNKNOWN"
        "SeriesDescription starts with Thigh"
        "SeriesDescription ends with Dixon"

      Numeric/symbolic operators (numeric when possible, string fallback):
        "EchoTime > 10"
        "FlipAngle <= 15"
        "RepetitionTime = 516"
        "SeriesNumber != 0"

      Presence operators:
        "InversionTime exists"
        "InversionTime missing"

    Tags can be keywords (EchoTime) or hex pairs (0018,0081).
    All filters must match for a file to be included (AND logic).

    Args:
        directory: Path to the directory to scan
        filters: List of filter strings
        recursive: Whether to scan subdirectories recursively
        mode: Result mode - "count" (just the number), "paths" (file paths),
              or "summary" (series breakdown of matches, default)
        response_format: Output format (markdown or json)
    """
    try:
        dir_path = Path(directory)
        if not dir_path.exists():
            return f"Error: Directory not found: {directory}"
        if not dir_path.is_dir():
            return f"Error: Path is not a directory: {directory}"

        if not filters:
            return "Error: Provide at least one filter."

        if mode not in ("count", "paths", "summary"):
            return f"Error: Invalid mode '{mode}'. Use 'count', 'paths', or 'summary'."

        # --- Parse and resolve filters ---
        parsed_filters = []
        for f_str in filters:
            parsed = _parse_filter(f_str)
            if parsed is None:
                return (
                    f"Error: Could not parse filter: '{f_str}'\n\n"
                    "Expected formats:\n"
                    "  Text:     TagName contains value\n"
                    "  Symbolic: TagName > value\n"
                    "  Presence: TagName exists"
                )
            tag_tuple, tag_name = _resolve_tag(parsed["tag_spec"])
            if tag_tuple is None:
                return f"Error: Could not resolve tag: '{parsed['tag_spec']}'"
            parsed["tag_tuple"] = tag_tuple
            parsed["tag_name"] = tag_name
            parsed_filters.append(parsed)

        # --- Scan files ---
        dicom_files, truncated = await asyncio.to_thread(
            _find_dicom_files, dir_path, recursive
        )

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

        # --- Apply filters ---
        matches: list[tuple[Path, pydicom.Dataset]] = []
        for file_path, ds in dicom_files:
            all_match = True
            for pf in parsed_filters:
                actual = _safe_get_tag(ds, pf["tag_tuple"])
                if not _apply_filter(pf, actual):
                    all_match = False
                    break
            if all_match:
                matches.append((file_path, ds))

        # --- Format filter description ---
        filter_desc = " AND ".join(
            f"{pf['tag_name']} {pf['operator']}"
            + (f" {pf['value']}" if pf["value"] is not None else "")
            for pf in parsed_filters
        )

        # --- Output based on mode ---
        if mode == "count":
            if response_format == ResponseFormat.JSON:
                result = {
                    "directory": str(dir_path),
                    "filters": filter_desc,
                    "total_scanned": len(dicom_files),
                    "truncated": truncated,
                    "match_count": len(matches),
                }
                return json.dumps(result, indent=2)
            else:
                output = [
                    "# DICOM Search Results\n",
                    f"**Filter**: {filter_desc}",
                    f"**Scanned**: {len(dicom_files)} files",
                    f"**Matches**: {len(matches)} files",
                ]
                if truncated:
                    output.append(
                        f"\n**Warning**: Scan truncated at {MAX_FILES} files."
                    )
                return "\n".join(output)

        elif mode == "paths":
            rel_paths = [str(fp.relative_to(dir_path)) for fp, _ in matches]
            if response_format == ResponseFormat.JSON:
                result = {
                    "directory": str(dir_path),
                    "filters": filter_desc,
                    "total_scanned": len(dicom_files),
                    "truncated": truncated,
                    "match_count": len(matches),
                    "paths": rel_paths,
                }
                return json.dumps(result, indent=2)
            else:
                output = [
                    "# DICOM Search Results\n",
                    f"**Filter**: {filter_desc}",
                    f"**Scanned**: {len(dicom_files)} files",
                    f"**Matches**: {len(matches)} files\n",
                ]
                if truncated:
                    output.append(
                        f"**Warning**: Scan truncated at {MAX_FILES} files.\n"
                    )
                if matches:
                    for p in rel_paths:
                        output.append(f"- {p}")
                else:
                    output.append("No files matched the filter criteria.")
                return "\n".join(output)

        else:  # summary
            # Group matches by series
            series_groups: Dict[str, Dict[str, Any]] = {}
            for fp, ds in matches:
                series_num = _safe_get_tag(ds, (0x0020, 0x0011))
                if series_num not in series_groups:
                    series_groups[series_num] = {
                        "series_number": series_num,
                        "series_description": _safe_get_tag(ds, (0x0008, 0x103E)),
                        "sequence_type": _identify_sequence_type(ds).value,
                        "file_count": 0,
                    }
                series_groups[series_num]["file_count"] += 1

            def _sort_key(item: tuple) -> tuple:
                sn = item[1]["series_number"]
                if sn is None or sn == "N/A":
                    return (2, "")
                try:
                    return (0, int(sn))
                except (ValueError, TypeError):
                    return (1, str(sn))

            sorted_series = sorted(series_groups.items(), key=_sort_key)

            if response_format == ResponseFormat.JSON:
                result = {
                    "directory": str(dir_path),
                    "filters": filter_desc,
                    "total_scanned": len(dicom_files),
                    "truncated": truncated,
                    "match_count": len(matches),
                    "series_count": len(sorted_series),
                    "series": [data for _, data in sorted_series],
                }
                return json.dumps(result, indent=2)
            else:
                output = [
                    "# DICOM Search Results\n",
                    f"**Filter**: {filter_desc}",
                    f"**Scanned**: {len(dicom_files)} files",
                    f"**Matches**: {len(matches)} files across {len(sorted_series)} series\n",
                ]
                if truncated:
                    output.append(
                        f"**Warning**: Scan truncated at {MAX_FILES} files.\n"
                    )
                if sorted_series:
                    headers = ["Series", "Description", "Type", "Files"]
                    rows = [
                        [
                            s["series_number"],
                            s["series_description"],
                            s["sequence_type"],
                            str(s["file_count"]),
                        ]
                        for _, s in sorted_series
                    ]
                    output.append(_format_markdown_table(headers, rows))
                else:
                    output.append("No files matched the filter criteria.")
                return "\n".join(output)

    except Exception as e:
        return f"Error searching DICOM files: {str(e)}"
