import 'package:flutter_test/flutter_test.dart';
import 'package:alt_client/src/contracts/alt_api_result.dart';
import 'package:alt_client/src/generated/alt/v1/backtest.pb.dart';
import 'package:alt_client/src/generated/alt/v1/common.pb.dart';
import 'package:alt_client/src/generated/alt/v1/market.pb.dart';

void main() {
  group('AltApiError mapping', () {
    test('fromErrorInfo preserves stable code and message', () {
      final info = ErrorInfo(code: 'NOT_FOUND', message: 'instrument missing');

      final error = AltApiError.fromErrorInfo(info);

      expect(error.code, equals('NOT_FOUND'));
      expect(error.message, equals('instrument missing'));
    });

    test('keeps empty fields when ErrorInfo is empty', () {
      final error = AltApiError.fromErrorInfo(ErrorInfo());

      expect(error.code, isEmpty);
      expect(error.message, isEmpty);
    });

    test('value equality is based on code and message', () {
      final a = AltApiError.fromErrorInfo(
        ErrorInfo(code: 'E1', message: 'm'),
      );
      final b = const AltApiError(code: 'E1', message: 'm');

      expect(a, equals(b));
      expect(a.hashCode, equals(b.hashCode));
    });
  });

  group('AltApiResult variants', () {
    test('success carries the decoded value', () {
      const result = AltApiResult<int>.success(42);

      expect(result, isA<AltApiSuccess<int>>());
      expect((result as AltApiSuccess<int>).value, equals(42));
    });

    test('apiError carries the mapped error', () {
      final error = AltApiError.fromErrorInfo(
        ErrorInfo(code: 'BAD', message: 'rejected'),
      );

      final result = AltApiResult<int>.apiError(error);

      expect(result, isA<AltApiFailure<int>>());
      expect((result as AltApiFailure<int>).error, equals(error));
    });

    test('transportFailure stays a stable state without ErrorInfo', () {
      const result = AltApiResult<int>.transportFailure(
        AltTransportKind.timeout,
        detail: 'no response within 5s',
      );

      expect(result, isA<AltTransportFailure<int>>());
      final failure = result as AltTransportFailure<int>;
      expect(failure.kind, equals(AltTransportKind.timeout));
      expect(failure.detail, equals('no response within 5s'));
    });

    test('transportFailure supports a bare kind with no detail', () {
      const result =
          AltApiResult<int>.transportFailure(AltTransportKind.disconnected);

      final failure = result as AltTransportFailure<int>;
      expect(failure.kind, equals(AltTransportKind.disconnected));
      expect(failure.detail, isNull);
    });

    test('exhaustive switch covers every variant', () {
      String describe(AltApiResult<int> result) {
        return switch (result) {
          AltApiSuccess<int>() => 'success',
          AltApiFailure<int>() => 'apiError',
          AltTransportFailure<int>() => 'transport',
        };
      }

      expect(describe(const AltApiResult<int>.success(1)), equals('success'));
      expect(
        describe(
          const AltApiResult<int>.apiError(
            AltApiError(code: 'E', message: 'm'),
          ),
        ),
        equals('apiError'),
      );
      expect(
        describe(
          const AltApiResult<int>.transportFailure(AltTransportKind.unknown),
        ),
        equals('transport'),
      );
    });
  });

  group('altResponseError extraction', () {
    test('reads typed error from each supported response', () {
      final error = ErrorInfo(code: 'X', message: 'boom');

      final responses = <Object>[
        ListInstrumentsResponse(error: error),
        ListBarsResponse(error: error),
        StartBacktestResponse(error: error),
        GetBacktestRunResponse(error: error),
        GetBacktestResultResponse(error: error),
        ListBacktestRunsResponse(error: error),
        GetBacktestRunDetailResponse(error: error),
        CompareBacktestRunsResponse(error: error),
      ];

      for (final response in responses) {
        final extracted = altResponseError(response);
        expect(extracted, isNotNull, reason: '${response.runtimeType}');
        expect(extracted!.code, equals('X'));
        expect(extracted.message, equals('boom'));
      }
    });

    test('returns null when a supported response has no error set', () {
      expect(altResponseError(ListInstrumentsResponse()), isNull);
      expect(altResponseError(ListBarsResponse()), isNull);
      expect(altResponseError(GetBacktestRunDetailResponse()), isNull);
    });

    test('returns null for unsupported response types', () {
      expect(altResponseError(HelloResponse()), isNull);
      expect(altResponseError(ListInstrumentsRequest()), isNull);
      expect(altResponseError('not a message'), isNull);
    });
  });
}
