import 'dart:async';

import 'package:flutter/foundation.dart';

import 'package:pshared/models/payment/payment.dart';
import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/resource.dart';
import 'package:pshared/service/payment/service.dart';
import 'package:pshared/utils/exception.dart';


class PaymentsProvider with ChangeNotifier {
  OrganizationsProvider? _organizations;
  String? _loadedOrganizationRef;

  Resource<List<Payment>> _resource = Resource(data: []);
  bool _isLoaded = false;
  bool _isLoadingMore = false;
  String? _nextCursor;
  int? _limit;
  String? _quotationRef;
  DateTime? _createdFrom;
  DateTime? _createdTo;
  List<String>? _states;

  int _opSeq = 0;

  Resource<List<Payment>> get resource => _resource;
  List<Payment> get payments => _resource.data ?? [];
  bool get isLoading => _resource.isLoading;
  Exception? get error => _resource.error;
  bool get isReady =>
      _isLoaded && !_resource.isLoading && _resource.error == null;

  bool get isLoadingMore => _isLoadingMore;
  String? get nextCursor => _nextCursor;
  bool get canLoadMore => _nextCursor != null && _nextCursor!.isNotEmpty;

  void update(OrganizationsProvider organizations) {
    _organizations = organizations;
    if (!organizations.isOrganizationSet) {
      reset();
      return;
    }

    final orgRef = organizations.current.id;
    if (_loadedOrganizationRef != orgRef) {
      _loadedOrganizationRef = orgRef;
      unawaited(refresh());
    }
  }

  Future<void> refresh({
    int? limit,
    String? quotationRef,
    DateTime? createdFrom,
    DateTime? createdTo,
    List<String>? states,
  }) async {
    await _refresh(
      limit: limit,
      quotationRef: quotationRef,
      createdFrom: createdFrom,
      createdTo: createdTo,
      states: states,
      showLoading: true,
      updateError: true,
    );
  }

  Future<void> refreshSilently({
    int? limit,
    String? quotationRef,
    DateTime? createdFrom,
    DateTime? createdTo,
    List<String>? states,
  }) async {
    await _refresh(
      limit: limit,
      quotationRef: quotationRef,
      createdFrom: createdFrom,
      createdTo: createdTo,
      states: states,
      showLoading: false,
      updateError: false,
    );
  }

  Future<void> refreshCurrentQuerySilently() async {
    await _refresh(
      limit: _limit,
      quotationRef: _quotationRef,
      createdFrom: _createdFrom,
      createdTo: _createdTo,
      states: _states,
      showLoading: false,
      updateError: false,
    );
  }

  void mergePayments(List<Payment> incoming) {
    if (incoming.isEmpty) return;
    final existing = List<Payment>.from(_resource.data ?? const []);
    final combined = <Payment>[...incoming, ...existing];
    final seen = <String>{};
    final merged = <Payment>[];

    for (final payment in combined) {
      final key = _paymentKey(payment);
      if (key == null || key.isEmpty) {
        merged.add(payment);
        continue;
      }
      if (seen.contains(key)) continue;
      seen.add(key);
      merged.add(payment);
    }

    _applyResource(_resource.copyWith(data: merged), notify: true);
  }

  Future<void> _refresh({
    int? limit,
    String? quotationRef,
    DateTime? createdFrom,
    DateTime? createdTo,
    List<String>? states,
    required bool showLoading,
    required bool updateError,
  }) async {
    final org = _organizations;
    if (org == null || !org.isOrganizationSet) return;

    _limit = limit;
    _quotationRef = _normalize(quotationRef);
    _createdFrom = createdFrom?.toUtc();
    _createdTo = createdTo?.toUtc();
    _states = _normalizeStates(states);
    _nextCursor = null;
    _isLoadingMore = false;

    final seq = ++_opSeq;

    if (showLoading) {
      _applyResource(
        _resource.copyWith(isLoading: true, error: null),
        notify: true,
      );
    }

    try {
      final page = await PaymentService.listPage(
        org.current.id,
        limit: _limit,
        cursor: null,
        quotationRef: _quotationRef,
        createdFrom: _createdFrom,
        createdTo: _createdTo,
        states: _states,
      );

      if (seq != _opSeq) return;

      _isLoaded = true;
      _nextCursor = _normalize(page.nextCursor);
      _applyResource(
        Resource(data: page.items, isLoading: false, error: null),
        notify: true,
      );
    } catch (e) {
      if (seq != _opSeq) return;
      if (updateError) {
        _applyResource(
          _resource.copyWith(isLoading: false, error: toException(e)),
          notify: true,
        );
      } else if (showLoading) {
        _applyResource(_resource.copyWith(isLoading: false), notify: true);
      }
    }
  }

  Future<void> loadMore() async {
    final org = _organizations;
    if (org == null || !org.isOrganizationSet) return;
    if (_isLoadingMore || _resource.isLoading) return;

    final cursor = _normalize(_nextCursor);
    if (cursor == null) return;

    final seq = _opSeq;

    _isLoadingMore = true;
    _applyResource(_resource.copyWith(error: null), notify: false);
    notifyListeners();

    try {
      final page = await PaymentService.listPage(
        org.current.id,
        limit: _limit,
        cursor: cursor,
        quotationRef: _quotationRef,
        createdFrom: _createdFrom,
        createdTo: _createdTo,
        states: _states,
      );

      if (seq != _opSeq) return;

      final combined = List<Payment>.from(payments)..addAll(page.items);
      _nextCursor = _normalize(page.nextCursor);

      _applyResource(
        _resource.copyWith(data: combined, error: null),
        notify: false,
      );
    } catch (e) {
      if (seq != _opSeq) return;

      _applyResource(_resource.copyWith(error: toException(e)), notify: false);
    } finally {
      if (seq == _opSeq) {
        _isLoadingMore = false;
        notifyListeners();
      }
    }
  }

  void reset() {
    _opSeq++;
    _isLoaded = false;
    _isLoadingMore = false;
    _nextCursor = null;
    _limit = null;
    _quotationRef = null;
    _createdFrom = null;
    _createdTo = null;
    _states = null;
    _resource = Resource(data: []);
    notifyListeners();
  }

  void _applyResource(
    Resource<List<Payment>> newResource, {
    required bool notify,
  }) {
    _resource = newResource;
    if (notify) notifyListeners();
  }

  String? _normalize(String? value) {
    final trimmed = value?.trim();
    if (trimmed == null || trimmed.isEmpty) return null;
    return trimmed;
  }

  String? _paymentKey(Payment payment) => _normalize(payment.paymentRef);

  List<String>? _normalizeStates(List<String>? states) {
    if (states == null || states.isEmpty) return null;
    final normalized = states
        .map((state) => state.trim())
        .where((state) => state.isNotEmpty)
        .toList();
    if (normalized.isEmpty) return null;
    return normalized;
  }
}
