# Copyright 2026 4f4d
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Core data models."""

from __future__ import annotations

from dataclasses import dataclass, field, asdict
from typing import Any, Dict, List, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    pass


@dataclass
class ClipContext:
    """Race context snapshot at the moment a radio clip was recorded.

    Fields are optional since context data isn't always available
    (formation lap, incomplete API data, etc.).
    """

    # Position & gaps
    position: Optional[int] = None
    gap_to_leader: Optional[str] = None
    interval_ahead: Optional[str] = None

    # Tyre info
    compound: Optional[str] = None
    stint_number: Optional[int] = None
    tyre_age: Optional[int] = None

    # Lap performance
    last_lap_time: Optional[str] = None
    best_lap_time: Optional[str] = None
    lap_number: Optional[int] = None

    # Nearby events (within ±10 seconds of the radio clip)
    events: List[str] = field(default_factory=list)

    # Nearby overtakes (within ±15 seconds)
    overtakes: List[str] = field(default_factory=list)

    # Nearby pit stops (within ±15 seconds)
    pit_stops: List[str] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        """Convert context to a flat dictionary."""
        return asdict(self)

    def __repr__(self) -> str:
        parts = []
        if self.position is not None:
            parts.append(f"P{self.position}")
        if self.compound:
            age_str = f" L{self.tyre_age}" if self.tyre_age is not None else ""
            parts.append(f"{self.compound}{age_str}")
        if self.gap_to_leader:
            parts.append(f"gap={self.gap_to_leader}")
        if self.events:
            parts.append(f"{len(self.events)} events")
        if self.overtakes:
            parts.append(f"{len(self.overtakes)} overtakes")
        return f"<ClipContext: {' | '.join(parts)}>" if parts else "<ClipContext: no data>"


@dataclass
class Clip:
    """A single team radio clip with metadata and race context."""

    driver: str
    driver_number: int
    driver_name: str
    team: str
    time: str
    date: str
    lap: Optional[int]
    recording_url: str
    local_path: Optional[str]
    context: ClipContext = field(default_factory=ClipContext)

    def play(self, block: bool = True) -> None:
        """Play the audio clip. Requires the [playback] extra."""
        from f1radio.playback import play_audio

        if not self.local_path:
            raise FileNotFoundError("No local audio file. Load the session first.")
        play_audio(self.local_path, block=block)

    def to_dict(self) -> Dict[str, Any]:
        """Flatten clip + context into a single dict."""
        d = {
            "driver": self.driver,
            "driver_number": self.driver_number,
            "driver_name": self.driver_name,
            "team": self.team,
            "time": self.time,
            "date": self.date,
            "lap": self.lap,
            "recording_url": self.recording_url,
            "local_path": self.local_path,
        }
        # Flatten context into the dict
        ctx = self.context.to_dict()
        for key, value in ctx.items():
            d[f"context_{key}"] = value
        return d

    def __repr__(self) -> str:
        pos = f" P{self.context.position}" if self.context.position else ""
        compound = f" {self.context.compound}" if self.context.compound else ""
        tyre_age = f" L{self.context.tyre_age}" if self.context.tyre_age is not None else ""
        return f"<Clip: {self.driver} @ {self.time}{pos}{compound}{tyre_age}>"


class ClipList(list):
    """A list of Clips with chainable filtering."""

    def filter(
        self,
        driver: Optional[Any] = None,
        after: Optional[str] = None,
        before: Optional[str] = None,
        lap: Optional[int] = None,
    ) -> "ClipList":
        """Filter by driver (code or number), time window, or lap. Returns a new ClipList."""
        from f1radio.utils import parse_time

        result = list(self)

        if driver is not None:
            if isinstance(driver, int):
                result = [c for c in result if c.driver_number == driver]
            else:
                driver_str = str(driver).upper().strip()
                result = [c for c in result if c.driver == driver_str]

        if after is not None:
            after_td = parse_time(after)
            result = [c for c in result if parse_time(c.time) >= after_td]

        if before is not None:
            before_td = parse_time(before)
            result = [c for c in result if parse_time(c.time) <= before_td]

        if lap is not None:
            result = [c for c in result if c.lap == lap]

        return ClipList(result)

    def __repr__(self) -> str:
        return f"<ClipList: {len(self)} clips>"


@dataclass
class Session:
    """A loaded F1 session with enriched team radio clips."""

    year: int
    race: str
    session_type: str
    session_key: int
    meeting_key: Optional[int]
    clips: ClipList = field(default_factory=ClipList)
    event_log: List[Dict[str, Any]] = field(default_factory=list)

    @property
    def drivers(self) -> List[str]:
        """Unique driver codes with radio clips, in order of first appearance."""
        seen = []
        for clip in self.clips:
            if clip.driver not in seen:
                seen.append(clip.driver)
        return seen

    @property
    def driver_counts(self) -> Dict[str, int]:
        """Radio clips per driver, most talkative first."""
        counts: Dict[str, int] = {}
        for clip in self.clips:
            counts[clip.driver] = counts.get(clip.driver, 0) + 1
        return dict(sorted(counts.items(), key=lambda x: x[1], reverse=True))

    @property
    def time_range(self) -> Optional[tuple]:
        """Tuple of (first_clip_time, last_clip_time), or None if no clips."""
        if not self.clips:
            return None
        times = [c.time for c in self.clips]
        return (times[0], times[-1])

    def export_json(self, path: str) -> None:
        """Export session data to JSON."""
        from f1radio.export import export_json

        export_json(self, path)

    def export_csv(self, path: str) -> None:
        """Export session data to CSV."""
        from f1radio.export import export_csv

        export_csv(self, path)

    def __len__(self) -> int:
        return len(self.clips)

    def __repr__(self) -> str:
        n_drivers = len(self.drivers)
        return (
            f"<Session: {self.year} {self.race} — {self.session_type} | "
            f"{len(self.clips)} clips | {n_drivers} drivers>"
        )
