import 'dart:async';
import 'dart:collection';

import 'package:flutter/foundation.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/network/failure.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval_draft.dart';
import 'package:superport_v2/features/approvals/domain/usecases/get_approval_draft_use_case.dart';
import 'package:superport_v2/features/approvals/domain/usecases/list_approval_drafts_use_case.dart';
import 'package:superport_v2/features/approvals/domain/usecases/save_approval_draft_use_case.dart';
import 'package:superport_v2/features/inventory/lookups/domain/entities/lookup_item.dart';
import 'package:superport_v2/features/inventory/lookups/domain/repositories/inventory_lookup_repository.dart';
import 'package:superport_v2/features/inventory/rental/presentation/models/rental_record.dart';
import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction.dart';
import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction_input.dart';
import 'package:superport_v2/features/inventory/transactions/domain/repositories/stock_transaction_repository.dart';
import 'package:superport_v2/features/inventory/transactions/presentation/services/transaction_detail_sync_service.dart';

/// 대여/반납 목록 데이터를 관리하는 컨트롤러.
class RentalController extends ChangeNotifier {
  RentalController({
    required StockTransactionRepository transactionRepository,
    required TransactionLineRepository lineRepository,
    required TransactionCustomerRepository customerRepository,
    required InventoryLookupRepository lookupRepository,
    List<String> fallbackStatusOptions = const ['대여중', '반납대기', '완료'],
    List<String> rentTransactionKeywords = const ['대여', 'rent'],
    List<String> returnTransactionKeywords = const ['반납', 'return'],
    SaveApprovalDraftUseCase? saveDraftUseCase,
    GetApprovalDraftUseCase? getDraftUseCase,
    ListApprovalDraftsUseCase? listDraftsUseCase,
  }) : _transactionRepository = transactionRepository,
       _lineRepository = lineRepository,
       _customerRepository = customerRepository,
       _lookupRepository = lookupRepository,
       _saveDraftUseCase = saveDraftUseCase,
       _getDraftUseCase = getDraftUseCase,
       _listDraftsUseCase = listDraftsUseCase,
       _fallbackStatusOptions = List<String>.unmodifiable(
         fallbackStatusOptions,
       ),
       _rentTransactionKeywords = List<String>.unmodifiable(
         rentTransactionKeywords,
       ),
       _returnTransactionKeywords = List<String>.unmodifiable(
         returnTransactionKeywords,
       ) {
    _statusOptions = List<String>.from(_fallbackStatusOptions);
  }

  final StockTransactionRepository _transactionRepository;
  final TransactionLineRepository _lineRepository;
  final TransactionCustomerRepository _customerRepository;
  final InventoryLookupRepository _lookupRepository;
  final SaveApprovalDraftUseCase? _saveDraftUseCase;
  final GetApprovalDraftUseCase? _getDraftUseCase;
  final ListApprovalDraftsUseCase? _listDraftsUseCase;
  final List<String> _fallbackStatusOptions;
  final List<String> _rentTransactionKeywords;
  final List<String> _returnTransactionKeywords;

  late List<String> _statusOptions;
  final Map<String, LookupItem> _statusLookup = {};
  LookupItem? _rentTransactionType;
  LookupItem? _returnTransactionType;
  PaginatedResult<StockTransaction>? _result;
  List<RentalRecord> _records = const [];
  bool _isLoading = false;
  String? _errorMessage;
  StockTransactionListFilter? _lastFilter;
  final Set<int> _processingTransactionIds = <int>{};
  bool _lastFilterByRentalTypes = true;
  StockTransactionApprovalInput? _approvalDraft;

  UnmodifiableListView<String> get statusOptions =>
      UnmodifiableListView(_statusOptions);

  UnmodifiableMapView<String, LookupItem> get statusLookup =>
      UnmodifiableMapView(_statusLookup);

  LookupItem? get rentTransactionType => _rentTransactionType;

  LookupItem? get returnTransactionType => _returnTransactionType;

  PaginatedResult<StockTransaction>? get result => _result;

  UnmodifiableListView<RentalRecord> get records =>
      UnmodifiableListView(_records);

  bool get isLoading => _isLoading;

  String? get errorMessage => _errorMessage;

  List<String> get fallbackStatusOptions => _fallbackStatusOptions;

  StockTransactionListFilter? get lastFilter => _lastFilter;

  UnmodifiableSetView<int> get processingTransactionIds =>
      UnmodifiableSetView(_processingTransactionIds);

  bool get lastFilterByRentalTypes => _lastFilterByRentalTypes;
  StockTransactionApprovalInput? get approvalDraft => _approvalDraft;

  /// 트랜잭션 상태 목록을 조회한다.
  Future<void> loadStatusOptions() async {
    try {
      final items = await _lookupRepository.fetchTransactionStatuses();
      final normalized = items
          .map((item) => item.name.trim())
          .where((name) => name.isNotEmpty)
          .toSet()
          .toList(growable: false);
      if (normalized.isEmpty) {
        return;
      }
      _statusOptions = normalized;
      _statusLookup
        ..clear()
        ..addEntries(
          items
              .where((item) => item.name.trim().isNotEmpty)
              .map((item) => MapEntry(item.name.trim(), item)),
        );
      notifyListeners();
    } catch (_) {
      // 실패 시 기본 값을 유지한다.
    }
  }

  /// 대여/반납 트랜잭션 타입을 모두 조회한다.
  Future<bool> resolveTransactionTypes() async {
    if (_rentTransactionType != null || _returnTransactionType != null) {
      return true;
    }
    try {
      final items = await _lookupRepository.fetchTransactionTypes();
      final rent = _matchLookup(items, _rentTransactionKeywords);
      final returns = _matchLookup(items, _returnTransactionKeywords);
      if (rent == null && returns == null) {
        return false;
      }
      _rentTransactionType = rent;
      _returnTransactionType = returns;
      notifyListeners();
      return true;
    } catch (_) {
      return false;
    }
  }

  /// 결재 구성 초안 상태를 갱신한다.
  void updateApprovalDraft(StockTransactionApprovalInput approval) {
    _approvalDraft = approval;
    notifyListeners();
    _persistApprovalDraft(approval);
  }

  /// 결재 구성 초안을 초기화한다.
  void clearApprovalDraft() {
    if (_approvalDraft == null) {
      return;
    }
    _approvalDraft = null;
    notifyListeners();
  }

  Future<void> loadApprovalDraftFromServer({
    required int requesterId,
    int? transactionId,
  }) async {
    final listUseCase = _listDraftsUseCase;
    final getUseCase = _getDraftUseCase;
    if (listUseCase == null || getUseCase == null) {
      return;
    }
    try {
      final result = await listUseCase.call(
        ApprovalDraftListFilter(
          requesterId: requesterId,
          transactionId: transactionId,
          pageSize: 10,
        ),
      );
      if (result.items.isEmpty) {
        return;
      }
      final sessionKey = _draftSessionKey(
        requesterId,
        transactionId: transactionId,
      );
      final summary = result.items.firstWhere(
        (item) => item.sessionKey == sessionKey,
        orElse: () => result.items.first,
      );
      final detail = await getUseCase.call(
        id: summary.id,
        requesterId: requesterId,
      );
      if (detail == null) {
        return;
      }
      _approvalDraft = _toTransactionApproval(detail);
      notifyListeners();
    } catch (error, stackTrace) {
      debugPrint('[RentalController] 초안 복구 실패: $error\n$stackTrace');
    }
  }

  /// 필터 조건에 맞는 대여/반납 트랜잭션을 조회한다.
  Future<void> fetchTransactions({
    required StockTransactionListFilter filter,
    bool filterByRentalTypes = true,
  }) async {
    _isLoading = true;
    _errorMessage = null;
    notifyListeners();

    try {
      _lastFilter = filter;
      _lastFilterByRentalTypes = filterByRentalTypes;
      final result = await _transactionRepository.list(filter: filter);
      final transactions = filterByRentalTypes
          ? result.items.where(_isRentalTransaction).toList(growable: false)
          : result.items;
      _records = transactions
          .map(RentalRecord.fromTransaction)
          .toList(growable: false);
      _result = PaginatedResult<StockTransaction>(
        items: transactions,
        page: filter.page,
        pageSize: filter.pageSize,
        total: transactions.length,
      );
    } catch (error) {
      _records = const [];
      _result = PaginatedResult<StockTransaction>(
        items: const [],
        page: filter.page,
        pageSize: filter.pageSize,
        total: 0,
      );
      final failure = Failure.from(error);
      _errorMessage = failure.describe();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  /// 마지막 필터를 사용해 목록을 새로고침한다.
  Future<void> refresh({
    StockTransactionListFilter? filter,
    bool? filterByRentalTypes,
  }) async {
    final targetFilter = filter ?? _lastFilter;
    if (targetFilter == null) {
      return;
    }
    await fetchTransactions(
      filter: targetFilter,
      filterByRentalTypes: filterByRentalTypes ?? _lastFilterByRentalTypes,
    );
  }

  /// 단일 대여/반납 트랜잭션 상세를 조회한다.
  Future<RentalRecord?> fetchTransactionDetail(
    int id, {
    List<String> include = const ['lines', 'customers', 'approval'],
  }) async {
    try {
      final transaction = await _transactionRepository.fetchDetail(
        id,
        include: include,
      );
      return RentalRecord.fromTransaction(transaction);
    } catch (error, stackTrace) {
      debugPrint('[RentalController] 상세 조회 실패(id=$id): $error');
      debugPrintStack(stackTrace: stackTrace);
      return null;
    }
  }

  void _persistApprovalDraft(StockTransactionApprovalInput approval) {
    final useCase = _saveDraftUseCase;
    if (useCase == null) {
      return;
    }
    if (approval.steps.isEmpty) {
      return;
    }
    final input = _buildApprovalDraftSaveInput(approval);
    if (!input.hasSteps) {
      return;
    }
    unawaited(
      Future<void>(() async {
        try {
          await useCase.call(input);
        } catch (error, stackTrace) {
          debugPrint('[RentalController] 초안 저장 실패: $error\n$stackTrace');
        }
      }),
    );
  }

  ApprovalDraftSaveInput _buildApprovalDraftSaveInput(
    StockTransactionApprovalInput approval,
  ) {
    final steps = approval.steps
        .map(
          (step) => ApprovalDraftStep(
            stepOrder: step.stepOrder,
            approverId: step.approverId,
            note: step.note,
          ),
        )
        .toList(growable: false);
    return ApprovalDraftSaveInput(
      requesterId: approval.requestedById,
      transactionId: null,
      templateId: approval.templateId,
      title: approval.title,
      summary: approval.summary,
      note: approval.note,
      metadata: approval.metadata,
      sessionKey: _draftSessionKey(approval.requestedById),
      statusId: approval.approvalStatusId,
      steps: steps,
    );
  }

  String _draftSessionKey(int requesterId, {int? transactionId}) {
    final base = 'inventory_rental_$requesterId';
    if (transactionId == null) {
      return base;
    }
    return '${base}_$transactionId';
  }

  StockTransactionApprovalInput _toTransactionApproval(
    ApprovalDraftDetail detail,
  ) {
    final submission = detail.toSubmissionInput(defaultStatusId: null);
    return StockTransactionApprovalInput(
      requestedById: submission.requesterId,
      approvalStatusId: submission.statusId == 0 ? null : submission.statusId,
      templateId: submission.templateId,
      finalApproverId: submission.finalApproverId,
      requestedAt: submission.requestedAt,
      decidedAt: submission.decidedAt,
      cancelledAt: submission.cancelledAt,
      lastActionAt: submission.lastActionAt,
      title: submission.title,
      summary: submission.summary,
      note: submission.note,
      metadata: submission.metadata,
      steps: submission.steps,
    );
  }

  /// 대여/반납 트랜잭션을 생성한다.
  Future<RentalRecord> createTransaction(
    StockTransactionCreateInput input, {
    bool refreshAfter = true,
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  }) {
    _approvalDraft = input.approval;
    return _executeMutation(
      () => _transactionRepository.create(input),
      refreshAfter: refreshAfter,
      refreshFilter: refreshFilter,
      refreshFilterByRentalTypes: refreshFilterByRentalTypes,
    );
  }

  /// 대여/반납 트랜잭션을 수정한다.
  Future<RentalRecord> updateTransaction(
    int id,
    StockTransactionUpdateInput input, {
    bool refreshAfter = true,
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  }) {
    return _executeMutation(
      () => _transactionRepository.update(id, input),
      trackedId: id,
      refreshAfter: refreshAfter,
      refreshFilter: refreshFilter,
      refreshFilterByRentalTypes: refreshFilterByRentalTypes,
    );
  }

  /// 대여/반납 트랜잭션을 삭제한다.
  Future<void> deleteTransaction(
    int id, {
    bool refreshAfter = true,
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  }) async {
    await _executeVoidMutation(
      () => _transactionRepository.delete(id),
      trackedId: id,
      refreshAfter: refreshAfter,
      refreshFilter: refreshFilter,
      refreshFilterByRentalTypes: refreshFilterByRentalTypes,
    );
  }

  /// 삭제된 대여/반납 트랜잭션을 복구한다.
  Future<RentalRecord> restoreTransaction(
    int id, {
    bool refreshAfter = true,
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  }) {
    return _executeMutation(
      () => _transactionRepository.restore(id),
      trackedId: id,
      refreshAfter: refreshAfter,
      refreshFilter: refreshFilter,
      refreshFilterByRentalTypes: refreshFilterByRentalTypes,
    );
  }

  /// 대여/반납 트랜잭션을 상신한다.
  Future<RentalRecord> submitTransaction(
    int id, {
    String? note,
    bool refreshAfter = true,
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  }) {
    return _executeMutation(
      () => _transactionRepository.submit(id, note: note),
      trackedId: id,
      refreshAfter: refreshAfter,
      refreshFilter: refreshFilter,
      refreshFilterByRentalTypes: refreshFilterByRentalTypes,
    );
  }

  /// 대여/반납 트랜잭션을 완료 처리한다.
  Future<RentalRecord> completeTransaction(
    int id, {
    String? note,
    bool refreshAfter = true,
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  }) {
    return _executeMutation(
      () => _transactionRepository.complete(id, note: note),
      trackedId: id,
      refreshAfter: refreshAfter,
      refreshFilter: refreshFilter,
      refreshFilterByRentalTypes: refreshFilterByRentalTypes,
    );
  }

  /// 대여/반납 트랜잭션을 승인 처리한다.
  Future<RentalRecord> approveTransaction(
    int id, {
    String? note,
    bool refreshAfter = true,
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  }) {
    return _executeMutation(
      () => _transactionRepository.approve(id, note: note),
      trackedId: id,
      refreshAfter: refreshAfter,
      refreshFilter: refreshFilter,
      refreshFilterByRentalTypes: refreshFilterByRentalTypes,
    );
  }

  /// 대여/반납 트랜잭션을 반려 처리한다.
  Future<RentalRecord> rejectTransaction(
    int id, {
    String? note,
    bool refreshAfter = true,
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  }) {
    return _executeMutation(
      () => _transactionRepository.reject(id, note: note),
      trackedId: id,
      refreshAfter: refreshAfter,
      refreshFilter: refreshFilter,
      refreshFilterByRentalTypes: refreshFilterByRentalTypes,
    );
  }

  /// 대여/반납 트랜잭션을 취소 처리한다.
  Future<RentalRecord> cancelTransaction(
    int id, {
    String? note,
    bool refreshAfter = true,
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  }) {
    return _executeMutation(
      () => _transactionRepository.cancel(id, note: note),
      trackedId: id,
      refreshAfter: refreshAfter,
      refreshFilter: refreshFilter,
      refreshFilterByRentalTypes: refreshFilterByRentalTypes,
    );
  }

  Future<RentalRecord> _executeMutation(
    Future<StockTransaction> Function() mutation, {
    int? trackedId,
    bool refreshAfter = true,
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  }) async {
    if (trackedId != null) {
      _processingTransactionIds.add(trackedId);
      notifyListeners();
    }
    _errorMessage = null;
    try {
      final transaction = await mutation();
      if (refreshAfter) {
        await _refreshAfterMutation(refreshFilter, refreshFilterByRentalTypes);
      } else {
        _upsertRecord(transaction);
      }
      return RentalRecord.fromTransaction(transaction);
    } catch (error) {
      final failure = Failure.from(error);
      _errorMessage = failure.describe();
      notifyListeners();
      rethrow;
    } finally {
      if (trackedId != null) {
        _processingTransactionIds.remove(trackedId);
        notifyListeners();
      }
    }
  }

  Future<void> _executeVoidMutation(
    Future<void> Function() mutation, {
    required int trackedId,
    bool refreshAfter = true,
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  }) async {
    _processingTransactionIds.add(trackedId);
    notifyListeners();
    _errorMessage = null;
    try {
      await mutation();
      if (refreshAfter) {
        await _refreshAfterMutation(refreshFilter, refreshFilterByRentalTypes);
      } else {
        _removeRecord(trackedId);
      }
    } catch (error) {
      final failure = Failure.from(error);
      _errorMessage = failure.describe();
      notifyListeners();
      rethrow;
    } finally {
      _processingTransactionIds.remove(trackedId);
      notifyListeners();
    }
  }

  Future<void> _refreshAfterMutation(
    StockTransactionListFilter? refreshFilter,
    bool? refreshFilterByRentalTypes,
  ) async {
    final filter = refreshFilter ?? _lastFilter;
    if (filter == null) {
      return;
    }
    await fetchTransactions(
      filter: filter,
      filterByRentalTypes:
          refreshFilterByRentalTypes ?? _lastFilterByRentalTypes,
    );
  }

  void _upsertRecord(StockTransaction transaction) {
    final record = RentalRecord.fromTransaction(transaction);
    final updatedRecords = _records.toList();
    final transactionId = transaction.id;
    if (transactionId != null) {
      final index = updatedRecords.indexWhere(
        (item) => item.id == transactionId,
      );
      if (index >= 0) {
        updatedRecords[index] = record;
      } else {
        updatedRecords.insert(0, record);
      }
    } else {
      updatedRecords.insert(0, record);
    }
    _records = updatedRecords;

    final currentResult = _result;
    if (currentResult != null) {
      final transactions = currentResult.items.toList();
      if (transactionId != null) {
        final index = transactions.indexWhere(
          (item) => item.id == transactionId,
        );
        if (index >= 0) {
          transactions[index] = transaction;
        } else {
          transactions.insert(0, transaction);
        }
      } else {
        transactions.insert(0, transaction);
      }
      _result = currentResult.copyWith(items: transactions);
    }
    notifyListeners();
  }

  void _removeRecord(int transactionId) {
    final updatedRecords = _records.toList()
      ..removeWhere((record) => record.id == transactionId);
    _records = updatedRecords;

    final currentResult = _result;
    if (currentResult != null) {
      final transactions = currentResult.items
          .where((item) => item.id != transactionId)
          .toList();
      _result = currentResult.copyWith(items: transactions);
    }
    notifyListeners();
  }

  bool _isRentalTransaction(StockTransaction transaction) {
    final rentId = _rentTransactionType?.id;
    final returnId = _returnTransactionType?.id;
    if (rentId != null && transaction.type.id == rentId) {
      return true;
    }
    if (returnId != null && transaction.type.id == returnId) {
      return true;
    }
    final name = transaction.type.name.toLowerCase();
    return name.contains('대여') ||
        name.contains('rent') ||
        name.contains('반납') ||
        name.contains('return');
  }

  LookupItem? _matchLookup(List<LookupItem> items, List<String> keywords) {
    if (items.isEmpty) {
      return null;
    }
    final normalizedKeywords = keywords
        .map((keyword) => keyword.toLowerCase())
        .toList(growable: false);
    LookupItem? fallback;
    for (final item in items) {
      final name = item.name.toLowerCase();
      final code = item.code?.toLowerCase();
      for (final keyword in normalizedKeywords) {
        if (name == keyword || name.contains(keyword)) {
          return item;
        }
        if (code != null && (code == keyword || code.contains(keyword))) {
          return item;
        }
      }
      if (fallback == null && item.isDefault) {
        fallback = item;
      }
    }
    return fallback ?? items.first;
  }

  /// 라인 변경 계획을 순차적으로 실행한다.
  Future<void> syncTransactionLines(
    int transactionId,
    TransactionLineSyncPlan plan,
  ) async {
    if (!plan.hasChanges) {
      return;
    }
    if (plan.updatedLines.isNotEmpty) {
      await _lineRepository.updateLines(transactionId, plan.updatedLines);
    }
    for (final lineId in plan.deletedLineIds) {
      await _lineRepository.deleteLine(lineId);
    }
    if (plan.createdLines.isNotEmpty) {
      await _lineRepository.addLines(transactionId, plan.createdLines);
    }
  }

  /// 고객 연결 변경 계획을 순차적으로 실행한다.
  Future<void> syncTransactionCustomers(
    int transactionId,
    TransactionCustomerSyncPlan plan,
  ) async {
    if (!plan.hasChanges) {
      return;
    }
    if (plan.updatedCustomers.isNotEmpty) {
      await _customerRepository.updateCustomers(
        transactionId,
        plan.updatedCustomers,
      );
    }
    for (final linkId in plan.deletedCustomerIds) {
      await _customerRepository.deleteCustomer(linkId);
    }
    if (plan.createdCustomers.isNotEmpty) {
      await _customerRepository.addCustomers(
        transactionId,
        plan.createdCustomers,
      );
    }
  }
}
