import 'dart:collection';
import 'dart:math';

import 'package:flutter_test/flutter_test.dart';
import 'package:relatica/friendica_client/paged_response.dart';
import 'package:relatica/friendica_client/pages_manager.dart';
import 'package:relatica/friendica_client/paging_data.dart';
import 'package:relatica/models/exec_error.dart';
import 'package:result_monad/result_monad.dart';

//Ensure works for ascending and descending tests
void main() async {
  test('Full range test', () async {
    final pm = _buildPagesManager();
    final numbers = <int>[];
    final initial = await pm.initialize(10);
    for (var e in initial.value.data) {
      numbers.add(e.id);
    }
    var current = initial.value;
    while (current.next != null) {
      final result = await pm.nextWithResult(current);
      current = result.value;
      for (var e in result.value.data) {
        numbers.add(e.id);
      }
    }

    current = initial.value;
    while (current.previous != null) {
      final result = await pm.previousWithResult(current);
      current = result.value;
      for (var e in result.value.data) {
        numbers.add(e.id);
      }
    }
    numbers.sort();
    final expected = elements.map((e) => e.id).toList();
    expected.sort();
    expect(numbers.length, equals(elements.length));
    expect(numbers, equals(expected));
    _checkPagesOrder(pm.pages);
  });

  test('End fills test', () async {
    final pm = _buildPagesManager();
    final numbers = <int>[];
    final initial = await pm.initialize(10);
    for (var e in initial.value.data.reversed) {
      numbers.add(e.id);
    }
    var moreWork = true;
    while (moreWork) {
      final nextFromEnd = await pm.nextFromEnd();
      final previousFromBeginning = await pm.previousFromBeginning();
      nextFromEnd.andThenSuccess(
          (r) => r.data.reversed.forEach((e) => numbers.add(e.id)));
      previousFromBeginning.andThenSuccess(
          (r) => r.data.forEach((e) => numbers.insert(0, e.id)));
      moreWork = nextFromEnd.isSuccess || previousFromBeginning.isSuccess;
    }

    for (var i = 0; i < numbers.length - 1; i++) {
      expect(numbers[i], greaterThan(numbers[i + 1]));
    }
    numbers.sort();
    expect(numbers.length, equals(elements.length));
    expect(numbers, equals(elements.map((e) => e.id)));
    _checkPagesOrder(pm.pages);
  });
}

void _checkPagesOrder(UnmodifiableListView<PagedResponse> pages) {
  expect(pages.first.previous, equals(null));
  expect(pages.last.next, equals(null));
  for (var i = 1; i < pages.length - 2; i++) {
    final p0 = pages[i];
    final p1 = pages[i + 1];
    expect(p0.previous!.minId, greaterThan(p1.previous!.minId!));
    expect(p0.next!.maxId, greaterThan(p1.next!.maxId!));
  }
}

class _DataElement {
  final int id;
  final int value;

  _DataElement({required this.id, required this.value});

  @override
  String toString() {
    return '_DataElement{id: $id}';
  }
}

const count = 1000;
final elements = List.generate(
    count, (index) => _DataElement(id: index, value: Random().nextInt(100)));

PagesManager<List<_DataElement>, int> _buildPagesManager() => PagesManager(
    idMapper: (data) => data.map((e) => e.id).toList(),
    onRequest: getDataElements);

FutureResult<PagedResponse<List<_DataElement>>, ExecError> getDataElements(
    PagingData page) async {
  final count = page.limit;
  late final int start;
  late final int stop;
  if (page.isLimitOnly) {
    stop = elements.length ~/ 2;
    start = stop - count;
  } else if (page.minId != null) {
    start = page.minId!;
    stop = start + count;
  } else if (page.maxId != null) {
    stop = page.maxId!;
    start = stop - count;
  } else {
    return buildErrorResult(
      type: ErrorType.serverError,
      message: 'Unknown paging type combo (only min and max supported)',
    );
  }

  int previous = stop;
  int next = start;
  final data = elements.sublist(max(0, start), min(elements.length, stop));
  return Result.ok(
    PagedResponse(
      data,
      previous: previous > elements.length - 1
          ? null
          : PagingData(limit: count, minId: previous),
      next: next < 0 ? null : PagingData(limit: count, maxId: next),
    ),
  );
}
