import 'package:flutter_test/flutter_test.dart';
import 'package:dio/dio.dart';

import 'package:lanebow/services/public_event_service.dart';
import 'package:lanebow/utils/web_storage.dart';

class FakeHttp implements PublicHttpClient {
  Response<dynamic>? lastResponse;
  Map<String, dynamic>? lastHeaders;
  String? lastPath;
  dynamic lastData;

  int statusCode = 200;
  Map<String, dynamic> responseData = const {'ok': true};

  @override
  Future<Response<dynamic>> get(String path, {Map<String, dynamic>? headers}) async {
    lastPath = path;
    lastData = null;
    lastHeaders = headers;
    if (statusCode >= 400) {
      throw DioException(
        requestOptions: RequestOptions(path: path),
        response: Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData),
        type: DioExceptionType.badResponse,
      );
    }
    lastResponse = Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData);
    return lastResponse!;
  }

  @override
  Future<Response<dynamic>> post(String path, {data, Map<String, dynamic>? headers}) async {
    lastPath = path;
    lastData = data;
    lastHeaders = headers;
    if (statusCode >= 400) {
      throw DioException(
        requestOptions: RequestOptions(path: path),
        response: Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData),
        type: DioExceptionType.badResponse,
      );
    }
    lastResponse = Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData);
    return lastResponse!;
  }

  @override
  Future<Response<dynamic>> put(String path, {data, Map<String, dynamic>? headers}) async {
    lastPath = path;
    lastData = data;
    lastHeaders = headers;
    if (statusCode >= 400) {
      throw DioException(
        requestOptions: RequestOptions(path: path),
        response: Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData),
        type: DioExceptionType.badResponse,
      );
    }
    lastResponse = Response(requestOptions: RequestOptions(path: path), statusCode: statusCode, data: responseData);
    return lastResponse!;
  }
}

void main() {
  group('PublicEventService.enter', () {
    test('saves token when response includes token', () async {
      final fake = FakeHttp()
        ..statusCode = 200
        ..responseData = {'token': 'TKN', 'event': {'id': 1}};
      final svc = PublicEventService.forTest(fake);
      final hash = 'abc123';

      // ensure empty
      WebStorage.removeItem(PublicEventService.tokenKey(hash));

      final data = await svc.enter(hash, password: 'pw');
      expect(data['token'], 'TKN');
      expect(WebStorage.getItem(PublicEventService.tokenKey(hash)), 'TKN');
      expect(fake.lastPath, '/api/public/' + hash);
    });

    test('removes saved token on 401/403', () async {
      final fake = FakeHttp()
        ..statusCode = 401
        ..responseData = {'message': 'unauthorized'};
      final svc = PublicEventService.forTest(fake);
      final hash = 'abc401';

      // preset token
      WebStorage.setItem(PublicEventService.tokenKey(hash), 'OLD');

      await expectLater(
        svc.enter(hash, password: 'wrong'),
        throwsA(isA<DioException>()),
      );
      expect(WebStorage.getItem(PublicEventService.tokenKey(hash)), isNull);
    });
  });

  group('PublicEventService.updateScore', () {
    test('sends Authorization header when token saved', () async {
      final fake = FakeHttp()
        ..statusCode = 200
        ..responseData = {'ok': true};
      final svc = PublicEventService.forTest(fake);
      final hash = 'hhh';
      WebStorage.setItem(PublicEventService.tokenKey(hash), 'TKN2');

      await svc.updateScore(
        hash,
        participantId: '10',
        gameNumber: 1,
        score: 200,
        handicap: 0,
      );

      expect(fake.lastPath, '/api/public/' + hash + '/score');
      expect(fake.lastHeaders?['Authorization'], 'Bearer TKN2');
      expect(fake.lastData['participantId'], '10');
      expect(fake.lastData['gameNumber'], 1);
      expect(fake.lastData['score'], 200);
    });
  });

  group('PublicEventService.fetchEvent', () {
    test('uses GET and Authorization header when token saved', () async {
      final fake = FakeHttp()
        ..statusCode = 200
        ..responseData = {'event': {'id': 'E1'}};
      final svc = PublicEventService.forTest(fake);
      final hash = 'fetch1';
      WebStorage.setItem(PublicEventService.tokenKey(hash), 'TKF');

      final data = await svc.fetchEvent(hash);
      expect(data['event']['id'], 'E1');
      expect(fake.lastPath, '/api/public/' + hash);
      expect(fake.lastHeaders?['Authorization'], 'Bearer TKF');
    });
  });

  group('PublicEventService.enter edge cases', () {
    test('404 does not remove saved token', () async {
      final fake = FakeHttp()
        ..statusCode = 404
        ..responseData = {'message': 'not found'};
      final svc = PublicEventService.forTest(fake);
      final hash = 'nf404';
      WebStorage.setItem(PublicEventService.tokenKey(hash), 'KEEP');
      await expectLater(svc.enter(hash), throwsA(isA<DioException>()));
      expect(WebStorage.getItem(PublicEventService.tokenKey(hash)), 'KEEP');
    });
  });

  group('PublicEventService.addGuest', () {
    test('sends payload and header', () async {
      final fake = FakeHttp()
        ..statusCode = 200
        ..responseData = {'ok': true};
      final svc = PublicEventService.forTest(fake);
      final hash = 'guest';
      WebStorage.setItem(PublicEventService.tokenKey(hash), 'TG');

      await svc.addGuest(hash, name: '홍길동', phone: '010', gender: 'M', average: 190);
      expect(fake.lastPath, '/api/public/' + hash + '/guest');
      expect(fake.lastHeaders?['Authorization'], 'Bearer TG');
      expect(fake.lastData['name'], '홍길동');
      expect(fake.lastData['phone'], '010');
      expect(fake.lastData['gender'], 'M');
      expect(fake.lastData['average'], 190);
    });
  });

  group('PublicEventService.updateScore errors', () {
    test('propagates server error 500', () async {
      final fake = FakeHttp()
        ..statusCode = 500
        ..responseData = {'message': 'oops'};
      final svc = PublicEventService.forTest(fake);
      final hash = 'err';
      WebStorage.setItem(PublicEventService.tokenKey(hash), 'TE');

      await expectLater(
        svc.updateScore(hash, participantId: 'p1', gameNumber: 1, score: 100),
        throwsA(isA<DioException>()),
      );
    });
  });
}
