"""Types."""

import collections
import datetime
import typing
from collections import Counter
from dataclasses import dataclass, field

from pycountry.db import Country

import agenda
from agenda import format_list_with_ampersand

StrDict = dict[str, typing.Any]
DateOrDateTime = datetime.datetime | datetime.date


def as_date(d: DateOrDateTime) -> datetime.date:
    """Convert datetime to date."""
    if isinstance(d, datetime.datetime):
        return d.date()
    assert isinstance(d, datetime.date)
    return d


def as_datetime(d: DateOrDateTime) -> datetime.datetime:
    """Date/time of event."""
    t0 = datetime.datetime.min.time()
    return (
        d
        if isinstance(d, datetime.datetime)
        else datetime.datetime.combine(d, t0).replace(tzinfo=datetime.timezone.utc)
    )


@dataclass
class TripElement:
    """Trip element."""

    when: DateOrDateTime
    title: str
    element_type: str
    detail: StrDict


@dataclass
class Trip:
    """Trip."""

    start: datetime.date
    travel: list[StrDict] = field(default_factory=list)
    accommodation: list[StrDict] = field(default_factory=list)
    conferences: list[StrDict] = field(default_factory=list)
    events: list[StrDict] = field(default_factory=list)
    name: str | None = None
    private: bool = False

    @property
    def title(self) -> str:
        """Trip title."""
        if self.name:
            return self.name
        titles: list[str] = [conf["name"] for conf in self.conferences] + [
            event["title"] for event in self.events
        ]
        if not titles:
            for travel in self.travel:
                if travel["depart"] and travel["depart"].date() != self.start:
                    place = travel["from"]
                    if place not in titles:
                        titles.append(place)
                if travel["depart"] and travel["depart"].date() != self.end:
                    place = travel["to"]
                    if place not in titles:
                        titles.append(place)

        return format_list_with_ampersand(titles) or "[unnamed trip]"

    @property
    def end(self) -> datetime.date | None:
        """End date for trip."""
        max_conference_end = (
            max(as_date(item["end"]) for item in self.conferences)
            if self.conferences
            else datetime.date.min
        )
        assert isinstance(max_conference_end, datetime.date)

        arrive = [as_date(item["arrive"]) for item in self.travel if "arrive" in item]
        travel_end = max(arrive) if arrive else datetime.date.min
        assert isinstance(travel_end, datetime.date)

        accommodation_end = (
            max(as_date(item["to"]) for item in self.accommodation)
            if self.accommodation
            else datetime.date.min
        )
        assert isinstance(accommodation_end, datetime.date)

        max_date = max(max_conference_end, travel_end, accommodation_end)
        return max_date if max_date != datetime.date.min else None

    def locations(self) -> list[tuple[str, Country]]:
        """Locations for trip."""
        seen: set[tuple[str, str]] = set()
        items = []

        for item in self.conferences + self.accommodation + self.events:
            if "country" not in item or "location" not in item:
                continue
            key = (item["location"], item["country"])
            if key in seen:
                continue
            seen.add(key)

            country = agenda.get_country(item["country"])
            assert country
            items.append((item["location"], country))

        return items

    @property
    def countries(self) -> list[Country]:
        """Countries visited as part of trip, in order."""
        seen: set[str] = set()
        items: list[Country] = []
        for item in self.conferences + self.accommodation + self.events:
            if "country" not in item:
                continue
            if item["country"] in seen:
                continue
            seen.add(item["country"])
            country = agenda.get_country(item["country"])
            assert country
            items.append(country)

        return items

    @property
    def countries_str(self) -> str:
        """List of countries visited on this trip."""
        return format_list_with_ampersand(
            [f"{c.name} {c.flag}" for c in self.countries]
        )

    @property
    def locations_str(self) -> str:
        """List of countries visited on this trip."""
        return format_list_with_ampersand(
            [f"{location} ({c.name}) {c.flag}" for location, c in self.locations()]
        )

    @property
    def country_flags(self) -> str:
        """Countries flags for trip."""
        return "".join(c.flag for c in self.countries)

    def total_distance(self) -> float | None:
        """Total distance for trip."""
        return (
            sum(t["distance"] for t in self.travel)
            if all(t.get("distance") for t in self.travel)
            else None
        )

    def distances_by_transport_type(self) -> list[tuple[str, float]]:
        """Calculate the total distance travelled for each type of transport.

        Any travel item with a missing or None 'distance' field is ignored.
        """
        transport_distances: Counter[float] = Counter()

        for item in self.travel:
            distance = item.get("distance")
            if distance:
                transport_type = item.get("type", "unknown")
                transport_distances[transport_type] += distance

        return list(transport_distances.items())

    def elements(self) -> list[TripElement]:
        """Trip elements ordered by time."""
        elements: list[TripElement] = []

        for item in self.accommodation:
            title = "Airbnb" if item.get("operator") == "airbnb" else item["name"]
            start = TripElement(
                when=item["from"],
                title=title,
                detail=item,
                element_type="check-in",
            )

            elements.append(start)

            end = TripElement(
                when=item["to"],
                title=title,
                detail=item,
                element_type="check-out",
            )

            elements.append(end)

        for item in self.travel:
            if item["type"] == "flight":
                flight_from = item["from_airport"]
                flight_to = item["to_airport"]
                name = (
                    "✈️ "
                    + f"{flight_from['name']} ({flight_from['iata']}) -> "
                    + f"{flight_to['name']} ({flight_to['iata']})"
                )

                elements.append(
                    TripElement(
                        when=item["depart"],
                        title=name,
                        detail=item,
                        element_type="flight",
                    )
                )
            if item["type"] == "train":
                name = f"{item['from']} -> {item['to']}"
                elements.append(
                    TripElement(
                        when=item["depart"],
                        title=name,
                        detail=item,
                        element_type="train",
                    )
                )
            if item["type"] == "ferry":
                name = f"{item['from']} -> {item['to']}"
                elements.append(
                    TripElement(
                        when=item["depart"],
                        title=name,
                        detail=item,
                        element_type="ferry",
                    )
                )

        return sorted(elements, key=lambda e: as_datetime(e.when))

    def elements_grouped_by_day(self) -> list[tuple[datetime.date, list[TripElement]]]:
        """Group trip elements by day."""
        # Create a dictionary to hold lists of TripElements grouped by their date
        grouped_elements: collections.defaultdict[datetime.date, list[TripElement]] = (
            collections.defaultdict(list)
        )

        for element in self.elements():
            # Extract the date part of the 'when' attribute
            day = as_date(element.when)
            grouped_elements[day].append(element)

        # Convert the dictionary to a sorted list of tuples
        grouped_elements_list = sorted(grouped_elements.items())

        return grouped_elements_list


# Example usage:
# You would call the function with your travel list here to get the results.


@dataclass
class Holiday:
    """Holiay."""

    name: str
    country: str
    date: datetime.date
    local_name: str | None = None

    @property
    def display_name(self) -> str:
        """Format name for display."""
        return (
            f"{self.name} ({self.local_name})"
            if self.local_name and self.local_name != self.name
            else self.name
        )


emojis = {
    "market": "🧺",
    "us_presidential_election": "πŸ—³οΈπŸ‡ΊπŸ‡Έ",
    "bus_route_closure": "🚌❌",
    "meetup": "πŸ‘₯",
    "dinner": "🍷",
    "party": "🍷",
    "ba_voucher": "✈️",
    "accommodation": "🏨",  # alternative: 🧳
    "flight": "✈️",
    "conference": "🎀",
    "rocket": "πŸš€",
    "birthday": "🎈",
    "waste_schedule": "πŸ—‘οΈ",
    "economist": "πŸ“°",
    "running": "πŸƒ",
    "critical_mass": "🚴",
    "trip": "🧳",
    "hackathon": "πŸ’»",
}


@dataclass
class Event:
    """Event."""

    name: str
    date: DateOrDateTime
    end_date: DateOrDateTime | None = None
    title: str | None = None
    url: str | None = None
    going: bool | None = None

    @property
    def as_datetime(self) -> datetime.datetime:
        """Date/time of event."""
        d = self.date
        t0 = datetime.datetime.min.time()
        return (
            d
            if isinstance(d, datetime.datetime)
            else datetime.datetime.combine(d, t0).replace(tzinfo=datetime.timezone.utc)
        )

    @property
    def has_time(self) -> bool:
        """Event has a time associated with it."""
        return isinstance(self.date, datetime.datetime)

    @property
    def as_date(self) -> datetime.date:
        """Date of event."""
        return (
            self.date.date() if isinstance(self.date, datetime.datetime) else self.date
        )

    @property
    def end_as_date(self) -> datetime.date:
        """Date of event."""
        return (
            (
                self.end_date.date()
                if isinstance(self.end_date, datetime.datetime)
                else self.end_date
            )
            if self.end_date
            else self.as_date
        )

    @property
    def display_time(self) -> str | None:
        """Time for display on web page."""
        return (
            self.date.strftime("%H:%M")
            if isinstance(self.date, datetime.datetime)
            else None
        )

    @property
    def display_timezone(self) -> str | None:
        """Timezone for display on web page."""
        return (
            self.date.strftime("%z")
            if isinstance(self.date, datetime.datetime)
            else None
        )

    def display_duration(self) -> str | None:
        """Duration for display."""
        if self.end_as_date != self.as_date or not self.has_time:
            return None

        assert isinstance(self.date, datetime.datetime)
        assert isinstance(self.end_date, datetime.datetime)

        secs: int = int((self.end_date - self.date).total_seconds())

        hours: int = secs // 3600
        mins: int = (secs % 3600) // 60

        if mins == 0:
            return f"{hours:d}h"
        if hours == 0:
            return f"{mins:d} mins"

        return f"{hours:d}h {mins:02d} mins"

    def delta_days(self, today: datetime.date) -> str:
        """Return number of days from today as a string."""
        delta = (self.as_date - today).days

        match delta:
            case 0:
                return "today"
            case 1:
                return "1 day"
            case _:
                return f"{delta:,d} days"

    @property
    def display_date(self) -> str:
        """Date for display on web page."""
        if isinstance(self.date, datetime.datetime):
            return self.date.strftime("%a, %d, %b %Y %H:%M %z")
        else:
            return self.date.strftime("%a, %d, %b %Y")

    @property
    def display_title(self) -> str:
        """Name for display."""
        return self.title or self.name

    @property
    def emoji(self) -> str | None:
        """Emoji."""
        if self.title == "LHG Run Club":
            return "πŸƒπŸ»"
        return emojis.get(self.name)

    @property
    def title_with_emoji(self) -> str | None:
        """Title with optional emoji at the start."""
        title = self.title or self.name
        if title is None:
            return None
        emoji = self.emoji
        return f"{emoji} {title}" if emoji else title