import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/watch_together/models/playback_state.dart';
import 'package:plezy/watch_together/models/sync_message.dart';
import 'package:plezy/watch_together/models/watch_session.dart';

void main() {
  const fullState = PlaybackState(
    seq: 42,
    ratingKey: '12345',
    serverId: 'srv-1',
    mediaTitle: 'Some Episode',
    phase: PlaybackPhase.playing,
    anchorPositionMs: 90000,
    anchorHostTimeMs: 1718700000000,
    rate: 1.5,
    controlMode: ControlMode.anyone,
    waitingOn: ['peer-a', 'peer-b'],
    actorPeerId: 'peer-a',
    actionHint: PlaybackActionHint.seek,
  );

  group('PlaybackState', () {
    test('round-trips through map with all fields', () {
      expect(PlaybackState.fromMap(fullState.toMap()), fullState);
    });

    test('round-trips with optionals omitted and omits empty keys', () {
      const minimal = PlaybackState(
        seq: 1,
        ratingKey: 'rk',
        serverId: 'sid',
        phase: PlaybackPhase.loading,
        anchorPositionMs: 0,
        anchorHostTimeMs: 1000,
        rate: 1.0,
        controlMode: ControlMode.hostOnly,
      );
      final map = minimal.toMap();
      expect(map.containsKey('ti'), isFalse);
      expect(map.containsKey('w'), isFalse);
      expect(map.containsKey('ab'), isFalse);
      expect(map.containsKey('ah'), isFalse);
      expect(PlaybackState.fromMap(map), minimal);
    });

    test('round-trips through the SyncMessage envelope', () {
      final message = SyncMessage.state(fullState, peerId: 'host-1');
      final decoded = SyncMessage.fromJson(message.toJson());
      expect(decoded.type, SyncMessageType.state);
      expect(decoded.state, fullState);
      expect(decoded.peerId, 'host-1');
    });

    test('unknown enum indexes decode to safe fallbacks instead of throwing', () {
      final map = fullState.toMap()
        ..['ph'] = 99
        ..['ah'] = 99
        ..['cm'] = 99;
      final decoded = PlaybackState.fromMap(map);
      expect(decoded.phase, PlaybackPhase.paused);
      expect(decoded.actionHint, isNull);
      expect(decoded.controlMode, ControlMode.hostOnly);
    });

    group('targetPositionMs', () {
      test('extrapolates from the anchor while playing', () {
        final target = fullState.targetPositionMs(fullState.anchorHostTimeMs + 2000);
        expect(target, 90000 + (2000 * 1.5).round());
      });

      test('clamps to the anchor before a scheduled start', () {
        expect(fullState.targetPositionMs(fullState.anchorHostTimeMs - 5000), 90000);
      });

      test('returns the anchor for non-playing phases', () {
        final paused = fullState.copyWith(phase: PlaybackPhase.paused);
        expect(paused.targetPositionMs(fullState.anchorHostTimeMs + 60000), 90000);
      });
    });
  });

  group('PeerStatus', () {
    test('round-trips through map and envelope', () {
      const status = PeerStatus(mediaKey: 'srv-1:12345', ready: true, buffering: false, positionMs: 1234, rttMs: 80);
      expect(PeerStatus.fromMap(status.toMap()), status);

      final decoded = SyncMessage.fromJson(SyncMessage.status(status, peerId: 'guest-1').toJson());
      expect(decoded.type, SyncMessageType.status);
      expect(decoded.status, status);
    });

    test('omits rtt when unknown', () {
      const status = PeerStatus(mediaKey: 'k', ready: false, buffering: true, positionMs: 0);
      expect(status.toMap().containsKey('rtt'), isFalse);
      expect(PeerStatus.fromMap(status.toMap()), status);
    });
  });

  group('ControlRequest', () {
    test('round-trips all kinds', () {
      const requests = [
        ControlRequest(kind: ControlRequestKind.play, positionMs: 5000),
        ControlRequest(kind: ControlRequestKind.pause),
        ControlRequest(kind: ControlRequestKind.seek, positionMs: 60000),
        ControlRequest(kind: ControlRequestKind.rate, rate: 1.25),
      ];
      for (final request in requests) {
        expect(ControlRequest.fromMap(request.toMap()), request);
        final decoded = SyncMessage.fromJson(SyncMessage.control(request, peerId: 'g').toJson());
        expect(decoded.control, request);
      }
    });
  });

  group('SyncMessage v3 envelope', () {
    test('join carries sync protocol 3', () {
      final join = SyncMessage.join(peerId: 'p', displayName: 'Name', isHost: false);
      final decoded = SyncMessage.fromJson(join.toJson());
      expect(decoded.version, 3);
      expect(decoded.version, SyncMessage.protocolVersion);
    });

    test('requestState round-trips', () {
      final decoded = SyncMessage.fromJson(SyncMessage.requestState(peerId: 'p').toJson());
      expect(decoded.type, SyncMessageType.requestState);
      expect(decoded.peerId, 'p');
    });

    test('copyWith preserves v3 payloads', () {
      final relabeled = SyncMessage.state(fullState).copyWith(peerId: 'relay-id');
      expect(relabeled.state, fullState);
      expect(relabeled.peerId, 'relay-id');
    });
  });
}
