import 'package:flutter/foundation.dart';

import '../../../../approvals/domain/entities/approval.dart';
import '../../../../approvals/domain/entities/approval_flow.dart';
import '../../../../approvals/domain/entities/approval_template.dart';
import '../../../../approvals/domain/repositories/approval_template_repository.dart';
import '../../../../approvals/domain/usecases/apply_approval_template_use_case.dart';
import '../../../../approvals/domain/usecases/save_approval_template_use_case.dart';
import '../../../../inventory/transactions/domain/entities/stock_transaction_input.dart';

/// 결재 요청 화면에서 사용하는 참가자 요약 정보.
///
/// - 상신자(requester)와 승인자(approver)에 공통으로 적용한다.
class ApprovalRequestParticipant {
  const ApprovalRequestParticipant({
    required this.id,
    required this.name,
    required this.employeeNo,
  });

  final int id;
  final String name;
  final String employeeNo;

  /// [ApprovalRequester]로 변환한다.
  ApprovalRequester toRequester() {
    return ApprovalRequester(id: id, employeeNo: employeeNo, name: name);
  }

  /// [ApprovalApprover]로 변환한다.
  ApprovalApprover toApprover() {
    return ApprovalApprover(id: id, employeeNo: employeeNo, name: name);
  }
}

/// 결재 요청 단계 상태를 표현한다.
class ApprovalRequestStep {
  const ApprovalRequestStep({
    required this.stepOrder,
    required this.approver,
    this.note,
  });

  final int stepOrder;
  final ApprovalRequestParticipant approver;
  final String? note;

  int get approverId => approver.id;

  ApprovalRequestStep copyWith({
    int? stepOrder,
    ApprovalRequestParticipant? approver,
    String? note,
  }) {
    return ApprovalRequestStep(
      stepOrder: stepOrder ?? this.stepOrder,
      approver: approver ?? this.approver,
      note: note ?? this.note,
    );
  }

  /// 도메인 계층에서 사용하는 [ApprovalStepAssignmentItem]으로 변환한다.
  ApprovalStepAssignmentItem toAssignmentItem() {
    return ApprovalStepAssignmentItem(
      stepOrder: stepOrder,
      approverId: approver.id,
      note: note,
    );
  }
}

/// 결재 템플릿 버전 정보를 보관한다.
class ApprovalTemplateSnapshot {
  const ApprovalTemplateSnapshot({
    required this.templateId,
    required this.updatedAt,
  });

  final int templateId;
  final DateTime? updatedAt;

  ApprovalTemplateSnapshot copyWith({int? templateId, DateTime? updatedAt}) {
    return ApprovalTemplateSnapshot(
      templateId: templateId ?? this.templateId,
      updatedAt: updatedAt ?? this.updatedAt,
    );
  }
}

/// 결재 요청 상태를 관리하고 검증/전송 모델로 변환하는 컨트롤러.
///
/// - 최대 98단계까지 결재 단계를 추가할 수 있으며, 승인자 중복을 방지한다.
/// - 마지막 단계 승인자가 최종 승인자로 자동 바인딩된다.
/// - 템플릿을 적용/변경할 때 버전 정보를 기록해 추후 비교에 활용한다.
class ApprovalRequestController extends ChangeNotifier {
  ApprovalRequestController({
    int maxSteps = 98,
    ApprovalTemplateRepository? templateRepository,
    SaveApprovalTemplateUseCase? saveTemplateUseCase,
    ApplyApprovalTemplateUseCase? applyTemplateUseCase,
  }) : assert(maxSteps > 0, 'maxSteps는 1 이상이어야 합니다.'),
       _maxSteps = maxSteps,
       _templateRepository = templateRepository,
       _saveTemplateUseCase = saveTemplateUseCase,
       _applyTemplateUseCase = applyTemplateUseCase;

  static const int defaultMaxSteps = 98;

  final int _maxSteps;
  final ApprovalTemplateRepository? _templateRepository;
  final SaveApprovalTemplateUseCase? _saveTemplateUseCase;
  final ApplyApprovalTemplateUseCase? _applyTemplateUseCase;

  ApprovalRequestParticipant? _requester;
  final List<ApprovalRequestStep> _steps = <ApprovalRequestStep>[];
  ApprovalTemplateSnapshot? _templateSnapshot;
  bool _isDirty = false;
  String? _errorMessage;
  bool _isApplyingTemplate = false;

  ApprovalRequestParticipant? get requester => _requester;
  List<ApprovalRequestStep> get steps => List.unmodifiable(_steps);
  int get maxSteps => _maxSteps;
  bool get hasReachedStepLimit => _steps.length >= _maxSteps;
  bool get hasDuplicateApprover =>
      _steps.map((step) => step.approverId).toSet().length != _steps.length;
  bool get hasRequesterConflict {
    final requester = _requester;
    if (requester == null) {
      return false;
    }
    return _steps.any((step) => step.approverId == requester.id);
  }

  int get totalSteps => _steps.length;
  String? get errorMessage => _errorMessage;
  bool get isDirty => _isDirty;
  bool get isApplyingTemplate => _isApplyingTemplate;
  ApprovalTemplateSnapshot? get templateSnapshot => _templateSnapshot;

  ApprovalRequestParticipant? get finalApprover {
    if (_steps.isEmpty) {
      return null;
    }
    return _steps.last.approver;
  }

  int? get finalApproverId => finalApprover?.id;

  /// 상신자를 설정한다.
  void setRequester(ApprovalRequestParticipant? participant) {
    if (_requester == participant) {
      return;
    }
    _requester = participant;
    if (participant != null &&
        _steps.any((step) => step.approverId == participant.id)) {
      _markDirty();
      _setError('상신자는 승인자로 지정할 수 없습니다.');
      return;
    }
    _isDirty = true;
    _clearError();
    notifyListeners();
  }

  /// 결재 단계를 추가한다.
  ///
  /// - 최대 단계 수를 초과하거나 중복 승인자를 추가하면 false를 반환한다.
  bool addStep({required ApprovalRequestParticipant approver, String? note}) {
    if (hasReachedStepLimit) {
      _setError('결재 단계는 최대 $_maxSteps개까지 추가할 수 있습니다.');
      return false;
    }
    final duplicated = _steps.any((step) => step.approverId == approver.id);
    if (_conflictsWithRequester(approver)) {
      _setError('상신자는 승인자로 지정할 수 없습니다.');
      return false;
    }
    if (duplicated) {
      _setError('동일한 승인자는 한 번만 추가할 수 있습니다.');
      return false;
    }
    _clearError();
    final step = ApprovalRequestStep(
      stepOrder: _steps.length + 1,
      approver: approver,
      note: note,
    );
    _steps.add(step);
    _markDirty();
    notifyListeners();
    return true;
  }

  /// 지정된 위치의 결재 단계를 제거한다.
  void removeStepAt(int index) {
    if (index < 0 || index >= _steps.length) {
      return;
    }
    _clearError();
    _steps.removeAt(index);
    _reassignStepOrders();
    _markDirty();
    notifyListeners();
  }

  /// 결재 단계의 순서를 이동한다.
  void moveStep(int oldIndex, int newIndex) {
    if (oldIndex < 0 ||
        oldIndex >= _steps.length ||
        newIndex < 0 ||
        newIndex >= _steps.length ||
        oldIndex == newIndex) {
      return;
    }
    _clearError();
    final step = _steps.removeAt(oldIndex);
    _steps.insert(newIndex, step);
    _reassignStepOrders();
    _markDirty();
    notifyListeners();
  }

  /// 결재 단계를 수정한다.
  ///
  /// - 승인자를 변경할 경우 중복 여부를 검사한다.
  bool updateStep(
    int index, {
    ApprovalRequestParticipant? approver,
    String? note,
  }) {
    if (index < 0 || index >= _steps.length) {
      return false;
    }
    final current = _steps[index];
    final nextApprover = approver ?? current.approver;
    final duplicated =
        approver != null &&
        _steps.asMap().entries.any(
          (entry) =>
              entry.key != index && entry.value.approverId == nextApprover.id,
        );
    if (_conflictsWithRequester(nextApprover)) {
      _setError('상신자는 승인자로 지정할 수 없습니다.');
      return false;
    }
    if (duplicated) {
      _setError('동일한 승인자는 한 번만 추가할 수 있습니다.');
      return false;
    }
    _clearError();
    _steps[index] = current.copyWith(approver: approver, note: note);
    _markDirty();
    notifyListeners();
    return true;
  }

  /// 최종 승인자를 지정한다.
  ///
  /// - 단계가 없으면 새로운 마지막 단계를 추가한다.
  /// - 이미 존재하는 경우 마지막 단계만 해당 승인자로 교체한다.
  bool setFinalApprover(ApprovalRequestParticipant approver, {String? note}) {
    if (_conflictsWithRequester(approver)) {
      _setError('최종 승인자는 상신자와 다른 사람이어야 합니다.');
      return false;
    }
    if (_steps.isEmpty) {
      return addStep(approver: approver, note: note);
    }
    final duplicateOtherIndex = _steps
        .sublist(0, _steps.length - 1)
        .any((step) => step.approverId == approver.id);
    if (duplicateOtherIndex) {
      _setError('최종 승인자는 다른 단계와 중복될 수 없습니다.');
      return false;
    }
    final lastIndex = _steps.length - 1;
    final last = _steps[lastIndex];
    _steps[lastIndex] = last.copyWith(
      approver: approver,
      note: note ?? last.note,
    );
    _clearError();
    _markDirty();
    notifyListeners();
    return true;
  }

  /// 템플릿 단계를 그대로 적용한다.
  void applyTemplateSteps(List<ApprovalRequestStep> steps) {
    if (_requester != null &&
        steps.any((step) => step.approverId == _requester!.id)) {
      _markDirty();
      _setError('상신자는 승인자로 지정할 수 없습니다.');
      return;
    }
    _steps
      ..clear()
      ..addAll(steps);
    _reassignStepOrders();
    _clearError();
    _markDirty();
    notifyListeners();
  }

  /// 템플릿 스냅샷을 기록한다.
  void setTemplateSnapshot(ApprovalTemplateSnapshot? snapshot) {
    _templateSnapshot = snapshot;
    _markDirty();
    notifyListeners();
  }

  /// 템플릿 버전이 최신인지 간단히 확인한다.
  bool isTemplateUpToDate(DateTime? serverUpdatedAt) {
    final snapshot = _templateSnapshot;
    if (snapshot == null) {
      return true;
    }
    if (snapshot.updatedAt == null || serverUpdatedAt == null) {
      return true;
    }
    return !snapshot.updatedAt!.isBefore(serverUpdatedAt);
  }

  /// 현재 상태로부터 결재 상신 입력 모델을 생성한다.
  ApprovalSubmissionInput buildSubmissionInput({
    int? transactionId,
    int? templateId,
    required int statusId,
    DateTime? requestedAt,
    DateTime? decidedAt,
    DateTime? cancelledAt,
    DateTime? lastActionAt,
    String? title,
    String? summary,
    String? note,
    Map<String, dynamic>? metadata,
  }) {
    final requester = _ensureRequester();
    final steps = _ensureSteps();
    return ApprovalSubmissionInput(
      transactionId: transactionId,
      templateId: templateId ?? _templateSnapshot?.templateId,
      statusId: statusId,
      requesterId: requester.id,
      finalApproverId: steps.isEmpty ? null : steps.last.approverId,
      requestedAt: requestedAt,
      decidedAt: decidedAt,
      cancelledAt: cancelledAt,
      lastActionAt: lastActionAt,
      title: title,
      summary: summary,
      note: note,
      metadata: metadata,
      steps: steps.map((step) => step.toAssignmentItem()).toList(),
    );
  }

  /// 재고 전표 결재 입력 모델로 변환한다.
  StockTransactionApprovalInput buildTransactionApprovalInput({
    int? approvalStatusId,
    int? templateId,
    DateTime? requestedAt,
    DateTime? decidedAt,
    DateTime? cancelledAt,
    DateTime? lastActionAt,
    String? title,
    String? summary,
    String? note,
    Map<String, dynamic>? metadata,
  }) {
    final requester = _ensureRequester();
    final steps = _ensureSteps();
    return StockTransactionApprovalInput(
      requestedById: requester.id,
      approvalStatusId: approvalStatusId,
      templateId: templateId ?? _templateSnapshot?.templateId,
      finalApproverId: steps.isEmpty ? null : steps.last.approverId,
      requestedAt: requestedAt,
      decidedAt: decidedAt,
      cancelledAt: cancelledAt,
      lastActionAt: lastActionAt,
      title: title,
      summary: summary,
      note: note,
      metadata: metadata,
      steps: steps.map((step) => step.toAssignmentItem()).toList(),
    );
  }

  /// 현재 상태를 초기화한다.
  void clear() {
    _requester = null;
    _steps.clear();
    _templateSnapshot = null;
    _errorMessage = null;
    _isDirty = false;
    notifyListeners();
  }

  /// 템플릿을 저장 후 상태를 갱신한다.
  ///
  /// 외부에서 저장 유즈케이스를 주입한 경우에만 동작한다.
  Future<ApprovalTemplate?> saveTemplate({
    int? templateId,
    required ApprovalTemplateInput input,
    List<ApprovalTemplateStepInput>? steps,
  }) async {
    final useCase = _saveTemplateUseCase;
    if (useCase == null) {
      throw StateError('SaveApprovalTemplateUseCase가 주입되지 않았습니다.');
    }
    final template = await useCase.call(
      templateId: templateId,
      input: input,
      steps: steps,
    );
    _templateSnapshot = ApprovalTemplateSnapshot(
      templateId: template.id,
      updatedAt: template.updatedAt,
    );
    _clearError();
    _markDirty();
    notifyListeners();
    return template;
  }

  /// 템플릿을 적용해 결재 단계를 갱신한다.
  ///
  /// - 템플릿 저장소와 Apply 유즈케이스가 모두 주입된 경우에만 지원한다.
  Future<ApprovalFlow?> applyTemplate({
    required int approvalId,
    required int templateId,
  }) async {
    final repository = _templateRepository;
    final useCase = _applyTemplateUseCase;
    if (repository == null || useCase == null) {
      throw StateError('템플릿 적용을 위한 의존성이 주입되지 않았습니다.');
    }
    _isApplyingTemplate = true;
    notifyListeners();
    try {
      final template = await repository.fetchDetail(
        templateId,
        includeSteps: true,
      );
      if (template.steps.isEmpty) {
        throw StateError('단계가 없는 템플릿은 적용할 수 없습니다.');
      }
      final flow = await useCase.call(
        approvalId: approvalId,
        templateId: templateId,
      );
      _templateSnapshot = ApprovalTemplateSnapshot(
        templateId: template.id,
        updatedAt: template.updatedAt,
      );
      final steps = template.steps
          .map(
            (step) => ApprovalRequestStep(
              stepOrder: step.stepOrder,
              approver: ApprovalRequestParticipant(
                id: step.approver.id,
                name: step.approver.name,
                employeeNo: step.approver.employeeNo,
              ),
              note: step.note,
            ),
          )
          .toList(growable: false);
      applyTemplateSteps(steps);
      return flow;
    } finally {
      _isApplyingTemplate = false;
      notifyListeners();
    }
  }

  void _setError(String message) {
    _errorMessage = message;
    notifyListeners();
  }

  void _clearError() {
    _errorMessage = null;
  }

  void _reassignStepOrders() {
    for (var index = 0; index < _steps.length; index++) {
      final current = _steps[index];
      _steps[index] = current.copyWith(stepOrder: index + 1);
    }
  }

  List<ApprovalRequestStep> _ensureSteps() {
    if (_steps.isEmpty) {
      throw StateError('최소 한 개 이상의 결재 단계를 추가해야 합니다.');
    }
    if (hasDuplicateApprover) {
      throw StateError('동일한 승인자가 중복되어 있습니다.');
    }
    final requester = _requester;
    if (requester != null) {
      for (var index = 0; index < _steps.length; index++) {
        final step = _steps[index];
        if (step.approverId != requester.id) {
          continue;
        }
        if (index == _steps.length - 1) {
          throw StateError('최종 승인자는 상신자와 다른 사람이어야 합니다.');
        }
        throw StateError('상신자는 승인자로 지정할 수 없습니다.');
      }
    }
    return List<ApprovalRequestStep>.unmodifiable(_steps);
  }

  ApprovalRequestParticipant _ensureRequester() {
    final requester = _requester;
    if (requester == null) {
      throw StateError('상신자를 선택해야 합니다.');
    }
    return requester;
  }

  void _markDirty() {
    _isDirty = true;
  }

  bool _conflictsWithRequester(ApprovalRequestParticipant participant) {
    final requester = _requester;
    if (requester == null) {
      return false;
    }
    return requester.id == participant.id;
  }
}
