import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
import 'package:uuid/uuid.dart';

import '../models/exec_error.dart';
import 'paging_data.dart';

final _logger = Logger('PagedResponse');

class PagedResponse<T> {
  String id;
  PagingData? previous;
  PagingData? next;
  T data;

  PagedResponse(this.data, {String? id, this.previous, this.next})
      : id = id ?? const Uuid().v4();

  bool get hasMorePages => previous != null || next != null;

  static Result<PagedResponse<T>, ExecError> fromLinkHeader<T>(
      String? linkHeader, T data) {
    if (linkHeader == null || linkHeader.isEmpty) {
      return Result.ok(PagedResponse(data));
    }

    String? previousPage;
    String? nextPage;
    for (String linkTerms in linkHeader.trim().split(',')) {
      if (linkHeader.isEmpty) {
        return buildErrorResult(
          type: ErrorType.parsingError,
          message: 'Link header element is blank',
        );
      }
      final paging = linkTerms.split(';');
      if (paging.length != 2) {
        return buildErrorResult(
          type: ErrorType.parsingError,
          message:
              'Incorrect number of elements, ${paging.length} != 2, for: $linkTerms',
        );
      }
      final urlPieceString = paging.first.trim();
      if (!urlPieceString.startsWith('<') && !urlPieceString.endsWith('>')) {
        return buildErrorResult(
          type: ErrorType.parsingError,
          message:
              'Link URL is malformed (no leading trailing <>): $urlPieceString',
        );
      }
      final url = urlPieceString.substring(1, urlPieceString.length - 1);
      final directionString = paging.last.trim();
      if (directionString == 'rel="prev"') {
        previousPage = url;
      } else if (directionString == 'rel="next"') {
        nextPage = url;
      } else {
        _logger.info('Unknown paging data: $directionString for url: $url');
      }
    }

    return Result.ok(PagedResponse(
      data,
      previous: previousPage == null
          ? null
          : PagingData.fromQueryParameters(
              Uri.parse(previousPage),
            ),
      next: nextPage == null
          ? null
          : PagingData.fromQueryParameters(
              Uri.parse(nextPage),
            ),
    ));
  }

  PagedResponse<T2> map<T2>(T2 Function(T data) func) => PagedResponse(
        func(data),
        previous: previous,
        next: next,
        id: id,
      );

  @override
  String toString() {
    return 'PagedResponse{previous: $previous, next: $next, data: $data}';
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is PagedResponse &&
          runtimeType == other.runtimeType &&
          previous == other.previous &&
          next == other.next &&
          data == other.data;

  @override
  int get hashCode => previous.hashCode ^ next.hashCode ^ data.hashCode;
}
