"""Comparison engine — normalize, score, rank, and format comparisons."""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from enum import StrEnum
from typing import Any


class SortOrder(StrEnum):
    ASC = "asc"
    DESC = "desc"


@dataclass
class ComparisonField:
    """Definition of a single field to compare across items."""

    name: str
    weight: float = 1.0
    higher_is_better: bool = True
    display_format: str = "{value}"  # e.g. "${value:.2f}", "{value}%"
    unit: str = ""


@dataclass
class ComparisonItem:
    """A single item in a comparison (e.g., one product/flight/hotel)."""

    name: str
    source: str = ""
    url: str = ""
    values: dict[str, float | str | bool | None] = field(default_factory=dict)
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class ScoredItem:
    """An item with its computed comparison scores."""

    item: ComparisonItem
    field_scores: dict[str, float] = field(default_factory=dict)
    total_score: float = 0.0
    rank: int = 0


@dataclass
class ComparisonResult:
    """Full comparison result with scored and ranked items."""

    fields: list[ComparisonField]
    items: list[ScoredItem]
    best_item: ScoredItem | None = None
    summary: str = ""


class ComparisonEngine:
    """Normalize, score, and rank items across multiple fields.

    Uses min-max normalization per field, then weighted scoring to
    produce a final ranking.
    """

    def compare(
        self,
        items: list[ComparisonItem],
        fields: list[ComparisonField],
    ) -> ComparisonResult:
        """Run a comparison across items and fields.

        Each field is normalized to 0-1 (min-max), multiplied by weight,
        then summed for a total score. Items are ranked by total score descending.
        """
        if not items or not fields:
            return ComparisonResult(fields=fields, items=[])

        scored = [ScoredItem(item=item) for item in items]

        for cf in fields:
            # Collect numeric values for this field
            numeric_vals: list[tuple[int, float]] = []
            for i, si in enumerate(scored):
                raw = si.item.values.get(cf.name)
                if raw is not None and isinstance(raw, int | float):
                    numeric_vals.append((i, float(raw)))

            if not numeric_vals:
                continue

            vals = [v for _, v in numeric_vals]
            min_v, max_v = min(vals), max(vals)
            spread = max_v - min_v

            for idx, val in numeric_vals:
                norm = 1.0 if spread == 0 else (val - min_v) / spread

                # Flip if lower is better (e.g. price)
                if not cf.higher_is_better:
                    norm = 1.0 - norm

                scored[idx].field_scores[cf.name] = norm * cf.weight

        # Sum field scores for total
        for si in scored:
            si.total_score = sum(si.field_scores.values())

        # Rank by total_score descending
        scored.sort(key=lambda s: s.total_score, reverse=True)
        for rank, si in enumerate(scored, 1):
            si.rank = rank

        best = scored[0] if scored else None

        summary = self._build_summary(scored, fields)

        return ComparisonResult(
            fields=fields,
            items=scored,
            best_item=best,
            summary=summary,
        )

    def to_table(self, result: ComparisonResult) -> str:
        """Format comparison as a text table."""
        if not result.items:
            return "No items to compare."

        lines: list[str] = []

        # Header
        headers = ["Rank", "Name", "Source"]
        for cf in result.fields:
            label = cf.name
            if cf.unit:
                label += f" ({cf.unit})"
            headers.append(label)
        headers.append("Score")

        lines.append(" | ".join(headers))
        lines.append("-" * len(lines[0]))

        # Rows
        for si in result.items:
            row = [str(si.rank), si.item.name, si.item.source]
            for cf in result.fields:
                raw = si.item.values.get(cf.name)
                if raw is not None:
                    try:
                        row.append(cf.display_format.format(value=raw))
                    except (ValueError, KeyError):
                        row.append(str(raw))
                else:
                    row.append("N/A")
            row.append(f"{si.total_score:.2f}")
            lines.append(" | ".join(row))

        return "\n".join(lines)

    def to_json(self, result: ComparisonResult) -> str:
        """Serialize comparison result to JSON."""
        data = {
            "fields": [
                {
                    "name": f.name,
                    "weight": f.weight,
                    "higher_is_better": f.higher_is_better,
                    "unit": f.unit,
                }
                for f in result.fields
            ],
            "items": [
                {
                    "rank": si.rank,
                    "name": si.item.name,
                    "source": si.item.source,
                    "url": si.item.url,
                    "values": si.item.values,
                    "field_scores": si.field_scores,
                    "total_score": si.total_score,
                }
                for si in result.items
            ],
            "best": result.best_item.item.name if result.best_item else None,
            "summary": result.summary,
        }
        return json.dumps(data, indent=2)

    def to_markdown(self, result: ComparisonResult) -> str:
        """Format comparison as a Markdown table."""
        if not result.items:
            return "No items to compare."

        lines: list[str] = []

        headers = ["Rank", "Name", "Source"]
        for cf in result.fields:
            label = cf.name
            if cf.unit:
                label += f" ({cf.unit})"
            headers.append(label)
        headers.append("Score")

        lines.append("| " + " | ".join(headers) + " |")
        lines.append("| " + " | ".join("---" for _ in headers) + " |")

        for si in result.items:
            row = [str(si.rank), si.item.name, si.item.source]
            for cf in result.fields:
                raw = si.item.values.get(cf.name)
                if raw is not None:
                    try:
                        row.append(cf.display_format.format(value=raw))
                    except (ValueError, KeyError):
                        row.append(str(raw))
                else:
                    row.append("N/A")
            best_marker = " **BEST**" if si.rank == 1 else ""
            row.append(f"{si.total_score:.2f}{best_marker}")
            lines.append("| " + " | ".join(row) + " |")

        if result.summary:
            lines.append("")
            lines.append(result.summary)

        return "\n".join(lines)

    def _build_summary(
        self,
        scored: list[ScoredItem],
        fields: list[ComparisonField],
    ) -> str:
        if not scored:
            return ""

        best = scored[0]
        parts = [f"**{best.item.name}** ranks #1"]

        if best.item.source:
            parts[0] += f" (from {best.item.source})"

        # Highlight best field values
        highlights: list[str] = []
        for cf in fields:
            val = best.item.values.get(cf.name)
            if val is not None:
                try:
                    formatted = cf.display_format.format(value=val)
                except (ValueError, KeyError):
                    formatted = str(val)
                highlights.append(f"{cf.name}: {formatted}")

        if highlights:
            parts.append("with " + ", ".join(highlights))

        return " ".join(parts) + "."
