"""FRAMETIME: external RTP-zero Anchor is an approved assumption, not clock sync."""
from dataclasses import replace
from pydantic import BaseModel, ConfigDict, Field
from app.contracts.frames import FrameTime


class FrameAnchor(BaseModel):
    model_config = ConfigDict(extra='forbid', frozen=True, strict=True)
    startWallTimeMs: int = Field(ge=0, le=2**53 - 1)
    synthetic: bool = False

    def frame_time(self):
        return FrameTime(anchor_wall_time_ms=self.startWallTimeMs, anchor_rtp_timestamp=0,
                         time_source='SYNTHETIC_TEST_ANCHOR' if self.synthetic else 'EXTERNAL_ANCHOR_RTP_ZERO')


def packet_values(packet):
    if type(packet) is not dict or set(packet) != {'timestamp', 'clock_rate', 'ssrc'}:
        raise ValueError('Invalid RTP metadata schema.')
    for key, limit in (('timestamp', 2**32 - 1), ('ssrc', 2**32 - 1), ('clock_rate', 2**31 - 1)):
        if type(packet[key]) is not int or not 0 <= packet[key] <= limit:
            raise ValueError('Invalid RTP metadata values.')
    if packet['clock_rate'] == 0:
        raise ValueError('RTP clock rate is unavailable.')
    return packet['timestamp'], packet['clock_rate'], packet['ssrc']


class RtpFrameClock:
    def __init__(self, anchor):
        self.anchor = anchor
        self.previous = None
        self.invalidated = False

    def resolve(self, packet):
        if packet is None:
            return self.anchor
        raw, rate, ssrc = packet_values(packet)
        if self.previous is not None:
            old, old_rate, old_ssrc = self.previous
            # Wrap/restart cannot silently reuse the previous cycle's zero Anchor.
            if raw < old or rate != old_rate or ssrc != old_ssrc:
                self.invalidated = True
        self.previous = raw, rate, ssrc
        valid = self.anchor is not None and not self.invalidated
        # Provider contract: startWallTimeMs is this RTP cycle's tick-zero wall time.
        # A reset invalidates it; never fetch a new Anchor or substitute the AI clock.
        wall = self.anchor.anchor_wall_time_ms + raw * 1000 // rate if valid else None
        if wall is not None and wall > 2**53 - 1:
            raise ValueError('RTP-derived time exceeds JSON integer range.')
        base = self.anchor or FrameTime()
        return replace(base, anchor_rtp_timestamp=0 if self.anchor else None,
                       source_rtp_timestamp=raw, source_rtp_timestamp_ext=None,
                       rtp_clock_rate=rate, source_time_wall_ms=wall,
                       sync_status='ESTIMATED' if valid else 'RAW_ONLY')


def validate_carried_time(value, anchor):
    if value is None:
        if anchor is not None:
            raise ValueError('Frame Anchor disappeared.')
        return None
    if type(value) is not dict or set(value) != set(FrameTime.__dataclass_fields__):
        raise ValueError('Invalid carried Frame time.')
    time = FrameTime(**value)
    if time.source_rtp_timestamp is None:
        if time != anchor:
            raise ValueError('Unexpected unobserved Frame time.')
        return time
    packet_values(dict(timestamp=time.source_rtp_timestamp, clock_rate=time.rtp_clock_rate, ssrc=0))
    if (time.anchor_id != (anchor.anchor_id if anchor else None) or
        time.anchor_wall_time_ms != (anchor.anchor_wall_time_ms if anchor else None) or
        time.anchor_rtp_timestamp != (0 if anchor else None) or
        time.time_source != (anchor.time_source if anchor else None) or
        time.source_rtp_timestamp_ext is not None):
        raise ValueError('Frame time belongs to a different Anchor.')
    if time.sync_status == 'ESTIMATED' and anchor is not None:
        wall = anchor.anchor_wall_time_ms + time.source_rtp_timestamp * 1000 // time.rtp_clock_rate
        if type(time.source_time_wall_ms) is not int or time.source_time_wall_ms != wall or wall > 2**53 - 1:
            raise ValueError('Frame time calculation mismatch.')
    elif time.sync_status != 'RAW_ONLY' or time.source_time_wall_ms is not None:
        raise ValueError('Unverified Frame time status.')
    return time
