import '../../about/external/data/Commitment.dart';
import '../../about/external/data/pages/request/HistoryRequest.dart';
import '../../about/external/data/pages/request/PageAndSort.dart';
import '../../about/external/data/pages/request/Pageable.dart';
import '../../about/external/data/pages/request/Sort.dart';
import '../../about/external/data/pages/response/CommitmentPage.dart';
import '../../about/external/initial/AbandonRequest.dart';
import '../../about/external/initial/AmnestyRequest.dart';
import '../../about/external/initial/CompletionRequest.dart';
import '../../about/external/initial/DeferralRequest.dart';
import '../../about/internal/application/CommitmentClass.dart';
import '../../about/internal/application/Standing.dart';
import '../../about/internal/application/UserDetails.dart';
import '../../utils/DebtEngine.dart';
import '../../utils/GuardrailEngine.dart';
import '../../utils/StandingEngine.dart';
import '../../utils/Thresholds.dart';
import '../../utils/ToneEngine.dart';
import '../parent/ParentViewModel.dart';
import 'ConnectOverdueQueue.dart';

class ViewOverdueQueue extends ParentViewModel {
  ConnectOverdueQueue connection;

  ViewOverdueQueue(super.context, this.connection);

  void loadQueue() async {
    if (!await hasNetwork(() => loadQueue())) return;

    showLoading("Loading what you owe");

    try {
      final response = await getDataManager().getOverdueQueue(HistoryRequest(
        query: PageAndSort(
          sort: Sort('desc', 'dueEnd'),
          page: Pageable(0, 0, 50, 0),
        ),
      ));

      final CommitmentPage page = CommitmentPage.fromJson(response.data);

      final UserDetails details = await getDataManager().getUserDetails();

      final double debt = DebtEngine.totalDebt(page.content);

      final Standing standing = StandingEngine.evaluate(
        debt,
        missedNonNegotiables: DebtEngine.missedNonNegotiables(page.content),
        sickMode: details.sickMode,
      );

      await getDataManager().setCachedDebtScore(debt);

      closeLoading();

      connection.onQueueLoaded(page.content, standing, debt);
    } catch (e) {
      handleError(e, () => loadQueue(), () => dismissError(), "Retry");
    }
  }

  /// Completion outside the due window is recorded as a late complete, not a
  /// complete. The server decides which — the client never claims one.
  void complete(Commitment item, CompletionRequest request) async {
    if (!await hasNetwork(() => complete(item, request))) return;

    showLoading("Recording");

    try {
      await getDataManager().completeCommitmentEntry(request);

      closeLoading();

      connection.onItemCleared(
        item,
        DebtEngine.reliefFromClearing(item),
        item.wouldBeLate ? "Late complete" : "Complete",
      );

      loadQueue();
    } catch (e) {
      handleError(
          e, () => complete(item, request), () => dismissError(), "Retry");
    }
  }

  /// The deferral gate. Non-negotiables never defer; everything else runs out
  /// of deferrals, after which the only routes left are completing or
  /// abandoning.
  void defer(Commitment item, String excuse) async {
    if (item.commitmentClass == CommitmentClass.NonNegotiable) {
      connection.onDeferralRefused(ToneEngine.nonNegotiableRefused());
      return;
    }

    if (!item.deferrableUnder(Thresholds.maxDeferralsPerTask)) {
      connection.onDeferralRefused(
          ToneEngine.deferralRefused(Thresholds.maxDeferralsPerTask));
      return;
    }

    if (excuse.trim().length < Thresholds.minExcuseLength) {
      connection
          .onDeferralRefused(ToneEngine.excuseTooShort(Thresholds.minExcuseLength));
      return;
    }

    if (!await hasNetwork(() => defer(item, excuse))) return;

    showLoading("Recording the deferral");

    try {
      // The new window opens tomorrow at the same time — a deferral moves the
      // window, it never removes it.
      final DateTime start =
          (item.dueStart ?? DateTime.now()).add(const Duration(days: 1));
      final DateTime end =
          (item.dueEnd ?? DateTime.now()).add(const Duration(days: 1));

      await getDataManager().deferCommitmentEntry(DeferralRequest(
        commitmentId: item.id ?? "",
        excuseText: excuse.trim(),
        newDueStart: start.toIso8601String(),
        newDueEnd: end.toIso8601String(),
      ));

      closeLoading();

      connection.onItemCleared(item, 0, "Deferred");

      loadQueue();
    } catch (e) {
      handleError(e, () => defer(item, excuse), () => dismissError(), "Retry");
    }
  }

  /// Abandoning costs the most debt of all, and resists decay for a month.
  void abandon(Commitment item, String reason) async {
    if (!await hasNetwork(() => abandon(item, reason))) return;

    showLoading("Recording");

    try {
      await getDataManager().abandonCommitmentEntry(AbandonRequest(
        commitmentId: item.id ?? "",
        reason: reason.trim(),
      ));

      closeLoading();

      connection.onItemCleared(item, 0, "Abandoned");

      loadQueue();
    } catch (e) {
      handleError(
          e, () => abandon(item, reason), () => dismissError(), "Retry");
    }
  }

  /// Rationed, so a bad flu does not destroy three months of progress.
  void spendAmnesty(Commitment item) async {
    final int spent = await getDataManager().getAmnestySpent();

    if (!GuardrailEngine.canSpendAmnesty(
        Thresholds.amnestyTokensPerMonth, spent)) {
      connection.onAmnestyRefused();
      return;
    }

    if (!await hasNetwork(() => spendAmnesty(item))) return;

    showLoading("Applying amnesty");

    try {
      await getDataManager()
          .spendAmnestyToken(AmnestyRequest(commitmentId: item.id ?? ""));

      await getDataManager().setAmnestySpent(spent + 1);

      closeLoading();

      connection.onAmnestySpent(GuardrailEngine.tokensRemaining(
          Thresholds.amnestyTokensPerMonth, spent + 1));

      loadQueue();
    } catch (e) {
      handleError(e, () => spendAmnesty(item), () => dismissError(), "Retry");
    }
  }
}
