import 'package:dio/dio.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/network/api_client.dart';
import 'package:superport_v2/core/network/api_error.dart';
import 'package:superport_v2/core/network/api_routes.dart';

import '../../domain/entities/approval.dart';
import '../../domain/entities/approval_proceed_status.dart';
import '../../domain/errors/approval_access_denied_exception.dart';
import '../../domain/repositories/approval_repository.dart';
import '../dtos/approval_audit_dto.dart';
import '../dtos/approval_dto.dart';
import '../dtos/approval_proceed_status_dto.dart';
import '../dtos/approval_request_dto.dart';

/// 결재 API 엔드포인트를 호출하는 원격 저장소 구현체.
///
/// - 모든 요청은 [ApiClient]를 통해 인증/에러 매핑을 공유한다.
/// - 엔티티 변환은 DTO 계층에 위임한다.
class ApprovalRepositoryRemote implements ApprovalRepository {
  ApprovalRepositoryRemote({required ApiClient apiClient}) : _api = apiClient;

  final ApiClient _api;

  static const _basePath = ApiRoutes.approvals;

  /// 결재 목록을 조회한다. 필터 조건이 없으면 최신순 페이지를 반환한다.
  @override
  Future<PaginatedResult<Approval>> list({
    int page = 1,
    int pageSize = 20,
    int? transactionId,
    int? approvalStatusId,
    int? requestedById,
    List<String>? statusCodes,
    bool includePending = false,
    bool includeHistories = false,
    bool includeSteps = false,
  }) async {
    final includeParts = <String>['requested_by', 'transaction'];
    if (includeSteps) {
      includeParts.add('steps');
    }
    if (includeHistories) {
      includeParts.add('histories');
    }
    final query = ApiClient.buildQuery(
      page: page,
      pageSize: pageSize,
      include: includeParts,
      filters: {
        if (transactionId != null) 'transaction_id': transactionId,
        if (approvalStatusId != null) 'approval_status_id': approvalStatusId,
        if (requestedById != null) 'requested_by_id': requestedById,
        if (statusCodes != null && statusCodes.isNotEmpty)
          'status': statusCodes,
        if (includePending) 'include_pending': includePending,
      },
    );
    return _guardApprovalAccess(() async {
      final response = await _api.get<Map<String, dynamic>>(
        _basePath,
        query: query,
        options: Options(responseType: ResponseType.json),
      );
      return ApprovalDto.parsePaginated(response.data ?? const {});
    });
  }

  /// 결재 상세를 조회한다. 단계/이력 포함 여부를 쿼리 파라미터로 제어한다.
  @override
  Future<Approval> fetchDetail(
    int id, {
    bool includeSteps = true,
    bool includeHistories = true,
  }) async {
    final includeParts = <String>['transaction', 'requested_by'];
    if (includeSteps) {
      includeParts.add('steps');
    }
    if (includeHistories) {
      includeParts.add('histories');
    }
    final query = ApiClient.buildQuery(include: includeParts);
    return _guardApprovalAccess(() async {
      final response = await _api.get<Map<String, dynamic>>(
        ApiClient.buildPath(_basePath, [id]),
        query: query.isEmpty ? null : query,
        options: Options(responseType: ResponseType.json),
      );
      return _mapApprovalFromResponse(response.data);
    });
  }

  @override
  Future<Approval> submit(ApprovalSubmissionInput input) async {
    final payload = ApprovalSubmitRequestDto(
      approval: ApprovalCreatePayloadDto.fromSubmission(input),
      steps: _mapSteps(input.steps),
    );
    final response = await _api.post<Map<String, dynamic>>(
      ApiRoutes.approvalAction('submit'),
      data: payload.toJson(),
      options: Options(responseType: ResponseType.json),
    );
    return _mapApprovalFromResponse(response.data);
  }

  @override
  Future<Approval> resubmit(ApprovalResubmissionInput input) async {
    final payload = ApprovalResubmitRequestDto(
      approvalId: input.approvalId,
      actorId: input.actorId,
      steps: _mapSteps(input.submission.steps),
      note: input.note,
      expectedUpdatedAt: input.expectedUpdatedAt,
      transactionExpectedUpdatedAt: input.transactionExpectedUpdatedAt,
    );
    final response = await _api.post<Map<String, dynamic>>(
      ApiRoutes.approvalAction('resubmit'),
      data: payload.toJson(),
      options: Options(responseType: ResponseType.json),
    );
    return _mapApprovalFromResponse(response.data);
  }

  @override
  Future<Approval> approve(ApprovalDecisionInput input) async {
    final payload = ApprovalDecisionRequestDto(
      approvalId: input.approvalId,
      actorId: input.actorId,
      note: input.note,
      expectedUpdatedAt: input.expectedUpdatedAt,
    );
    final response = await _api.post<Map<String, dynamic>>(
      ApiRoutes.approvalAction('approve'),
      data: payload.toJson(),
      options: Options(responseType: ResponseType.json),
    );
    return _mapApprovalFromResponse(response.data);
  }

  @override
  Future<Approval> reject(ApprovalDecisionInput input) async {
    final payload = ApprovalDecisionRequestDto(
      approvalId: input.approvalId,
      actorId: input.actorId,
      note: input.note,
      expectedUpdatedAt: input.expectedUpdatedAt,
    );
    final response = await _api.post<Map<String, dynamic>>(
      ApiRoutes.approvalAction('reject'),
      data: payload.toJson(),
      options: Options(responseType: ResponseType.json),
    );
    return _mapApprovalFromResponse(response.data);
  }

  @override
  Future<Approval> recall(ApprovalRecallInput input) async {
    final payload = ApprovalRecallRequestDto(
      approvalId: input.approvalId,
      actorId: input.actorId,
      note: input.note,
      expectedUpdatedAt: input.expectedUpdatedAt,
      transactionExpectedUpdatedAt: input.transactionExpectedUpdatedAt,
    );
    final response = await _api.post<Map<String, dynamic>>(
      ApiRoutes.approvalAction('recall'),
      data: payload.toJson(),
      options: Options(responseType: ResponseType.json),
    );
    return _mapApprovalFromResponse(response.data);
  }

  /// 활성화된 결재 행위 목록을 조회한다.
  @override
  Future<List<ApprovalAction>> listActions({bool activeOnly = true}) async {
    final query = ApiClient.buildQuery(
      page: 1,
      pageSize: 100,
      filters: {if (activeOnly) 'active': true},
    );
    final response = await _api.get<Map<String, dynamic>>(
      ApiRoutes.approvalActions,
      query: query,
      options: Options(responseType: ResponseType.json),
    );
    final items = (response.data?['items'] as List<dynamic>? ?? [])
        .whereType<Map<String, dynamic>>()
        .map(ApprovalActionDto.fromJson)
        .map((dto) => dto.toEntity())
        .toList();
    return items;
  }

  @override
  Future<PaginatedResult<ApprovalHistory>> listHistory({
    required int approvalId,
    int page = 1,
    int pageSize = 20,
    DateTime? from,
    DateTime? to,
    int? actorId,
    int? approvalActionId,
  }) async {
    final query = ApiClient.buildQuery(
      page: page,
      pageSize: pageSize,
      filters: {
        'approval_id': approvalId,
        if (from != null) 'action_from': from,
        if (to != null) 'action_to': to,
        if (actorId != null) 'approver_id': actorId,
        if (approvalActionId != null) 'approval_action_id': approvalActionId,
      },
    );
    final response = await _api.get<Map<String, dynamic>>(
      ApiRoutes.approvalHistory,
      query: query,
      options: Options(responseType: ResponseType.json),
    );
    final dto = ApprovalAuditListDto.fromJson(response.data ?? const {});
    return PaginatedResult<ApprovalHistory>(
      items: dto.items.map((e) => e.toEntity()).toList(growable: false),
      page: dto.page,
      pageSize: dto.pageSize,
      total: dto.total,
    );
  }

  /// 결재 단계 행위를 수행하고 업데이트된 결재 정보를 반환한다.
  @override
  Future<Approval> performStepAction(ApprovalStepActionInput input) async {
    final path = ApiClient.buildPath(ApiRoutes.approvalSteps, [
      input.stepId,
      'actions',
    ]);
    final response = await _api.post<Map<String, dynamic>>(
      path,
      data: input.toPayload(),
      options: Options(responseType: ResponseType.json),
    );
    final approvalJson = _extractApprovalFromActionResponse(
      response.data ?? const <String, dynamic>{},
    );
    if (approvalJson == null) {
      throw StateError('결재 단계 행위 응답에 결재 데이터가 없습니다.');
    }
    return ApprovalDto.fromJson(approvalJson).toEntity();
  }

  /// 결재 단계들을 일괄로 생성하거나 재배치한다.
  @override
  Future<Approval> assignSteps(ApprovalStepAssignmentInput input) async {
    final path = ApiClient.buildPath(_basePath, [input.approvalId, 'steps']);
    final response = await _api.post<Map<String, dynamic>>(
      path,
      data: input.toPayload(),
      options: Options(responseType: ResponseType.json),
    );
    final approvalJson = _extractApprovalFromActionResponse(
      response.data ?? const <String, dynamic>{},
    );
    if (approvalJson == null) {
      throw StateError('결재 단계 일괄 처리 응답에 결재 데이터가 없습니다.');
    }
    return ApprovalDto.fromJson(approvalJson).toEntity();
  }

  /// 결재가 다음 단계로 진행 가능한지 확인한다.
  @override
  Future<ApprovalProceedStatus> canProceed(int id) async {
    final response = await _api.get<Map<String, dynamic>>(
      ApiClient.buildPath(_basePath, [id, 'can-proceed']),
      options: Options(responseType: ResponseType.json),
    );
    return ApprovalProceedStatusDto.fromJson(
      _api.unwrapAsMap(response),
    ).toEntity();
  }

  /// 새로운 결재를 생성한다.
  @override
  Future<Approval> create(ApprovalCreateInput input) async {
    final response = await _api.post<Map<String, dynamic>>(
      _basePath,
      data: input.toPayload(),
      options: Options(responseType: ResponseType.json),
    );
    return _mapApprovalFromResponse(response.data);
  }

  /// 결재 기본 정보를 수정한다.
  @override
  Future<Approval> update(ApprovalUpdateInput input) async {
    final response = await _api.patch<Map<String, dynamic>>(
      ApiClient.buildPath(_basePath, [input.id]),
      data: input.toPayload(),
      options: Options(responseType: ResponseType.json),
    );
    return _mapApprovalFromResponse(response.data);
  }

  /// 결재를 삭제(비활성화)한다.
  @override
  Future<void> delete(int id) async {
    await _api.delete<void>(ApiClient.buildPath(_basePath, [id]));
  }

  /// 삭제된 결재를 복구한다.
  @override
  Future<Approval> restore(int id) async {
    final response = await _api.post<Map<String, dynamic>>(
      ApiClient.buildPath(_basePath, [id, 'restore']),
      options: Options(responseType: ResponseType.json),
    );
    return _mapApprovalFromResponse(response.data);
  }

  /// 결재 단계/행위 응답에서 결재 객체 JSON을 추출한다.
  Approval _mapApprovalFromResponse(Map<String, dynamic>? body) {
    final payload = _extractApprovalPayload(body);
    if (payload.isEmpty) {
      throw StateError('결재 응답에 결재 데이터가 없습니다.');
    }
    return ApprovalDto.fromJson(payload).toEntity();
  }

  Map<String, dynamic> _extractApprovalPayload(Map<String, dynamic>? body) {
    if (body == null || body.isEmpty) {
      return const <String, dynamic>{};
    }
    final data = body['data'];
    if (data is Map<String, dynamic>) {
      final approval = _selectApprovalPayload(data);
      if (approval != null) {
        return approval;
      }
      return Map<String, dynamic>.from(data);
    }
    final approval = _selectApprovalPayload(body);
    if (approval != null) {
      return approval;
    }
    return Map<String, dynamic>.from(body);
  }

  List<ApprovalStepInputDto> _mapSteps(List<ApprovalStepAssignmentItem> items) {
    return items.map(ApprovalStepInputDto.fromDomain).toList(growable: false);
  }

  /// 결재 단계/행위 응답에서 결재 객체 JSON을 추출한다.
  Map<String, dynamic>? _extractApprovalFromActionResponse(
    Map<String, dynamic> body,
  ) {
    final data = body['data'];
    final dataMap = data is Map<String, dynamic> ? data : null;
    Map<String, dynamic>? approval = _selectApprovalPayload(dataMap);
    approval ??= _selectApprovalPayload(body);
    if (approval == null) {
      return null;
    }

    final merged = Map<String, dynamic>.from(approval);
    if (dataMap != null) {
      final steps = _mergeStepsPayload(
        existing: merged['steps'],
        data: dataMap,
      );
      if (steps != null) {
        merged['steps'] = steps;
      }

      final histories = _mergeHistoriesPayload(
        existing: merged['histories'],
        data: dataMap,
      );
      if (histories != null) {
        merged['histories'] = histories;
      }
    }
    return merged;
  }

  Map<String, dynamic>? _selectApprovalPayload(Map<String, dynamic>? source) {
    if (source == null) {
      return null;
    }
    if (source['approval'] is Map<String, dynamic>) {
      return Map<String, dynamic>.from(
        source['approval'] as Map<String, dynamic>,
      );
    }
    if (source['approval_data'] is Map<String, dynamic>) {
      return Map<String, dynamic>.from(
        source['approval_data'] as Map<String, dynamic>,
      );
    }
    final hasStatus =
        source.containsKey('status') || source.containsKey('approval_status');
    if (source.containsKey('approval_no') && hasStatus) {
      return Map<String, dynamic>.from(source);
    }
    if (source['approval'] == null && source['data'] is Map<String, dynamic>) {
      return _selectApprovalPayload(source['data'] as Map<String, dynamic>);
    }
    return null;
  }

  List<Map<String, dynamic>>? _mergeStepsPayload({
    required dynamic existing,
    required Map<String, dynamic> data,
  }) {
    final steps = <Map<String, dynamic>>[];

    void upsert(Map<String, dynamic> step) {
      final id = step['id'] as int?;
      final order = step['step_order'] as int?;
      final index = steps.indexWhere((element) {
        final elementId = element['id'] as int?;
        if (elementId != null && id != null) {
          return elementId == id;
        }
        if (order != null) {
          return element['step_order'] == order;
        }
        return false;
      });
      final copy = Map<String, dynamic>.from(step);
      if (index >= 0) {
        steps[index] = copy;
      } else {
        steps.add(copy);
      }
    }

    if (existing is List) {
      for (final item in existing) {
        if (item is Map<String, dynamic>) {
          upsert(item);
        }
      }
    }

    final responseSteps = data['steps'];
    if (responseSteps is List) {
      for (final item in responseSteps) {
        if (item is Map<String, dynamic>) {
          upsert(item);
        }
      }
    }

    if (data['step'] is Map<String, dynamic>) {
      upsert(data['step'] as Map<String, dynamic>);
    }
    if (data['next_step'] is Map<String, dynamic>) {
      upsert(data['next_step'] as Map<String, dynamic>);
    }

    if (steps.isEmpty) {
      return existing is List
          ? existing
                .whereType<Map<String, dynamic>>()
                .map((step) => Map<String, dynamic>.from(step))
                .toList()
          : null;
    }

    steps.sort((a, b) {
      final orderA = a['step_order'] as int? ?? 0;
      final orderB = b['step_order'] as int? ?? 0;
      return orderA.compareTo(orderB);
    });
    return steps;
  }

  List<Map<String, dynamic>>? _mergeHistoriesPayload({
    required dynamic existing,
    required Map<String, dynamic> data,
  }) {
    final histories = <Map<String, dynamic>>[];

    void append(Map<String, dynamic> history) {
      histories.add(Map<String, dynamic>.from(history));
    }

    if (existing is List) {
      for (final item in existing) {
        if (item is Map<String, dynamic>) {
          append(item);
        }
      }
    }

    final responseHistories = data['histories'];
    if (responseHistories is List) {
      for (final item in responseHistories) {
        if (item is Map<String, dynamic>) {
          append(item);
        }
      }
    }

    if (data['history'] is Map<String, dynamic>) {
      append(data['history'] as Map<String, dynamic>);
    }

    if (histories.isEmpty) {
      return existing is List
          ? existing
                .whereType<Map<String, dynamic>>()
                .map((history) => Map<String, dynamic>.from(history))
                .toList()
          : null;
    }

    DateTime? parseTime(Map<String, dynamic> json) {
      String? read(dynamic value) {
        if (value is String && value.trim().isNotEmpty) {
          return value.trim();
        }
        return null;
      }

      final raw =
          read(json['action_at']) ??
          read(json['created_at']) ??
          read(json['updated_at']);
      if (raw == null) {
        return null;
      }
      return DateTime.tryParse(raw);
    }

    histories.sort((a, b) {
      final timeA = parseTime(a);
      final timeB = parseTime(b);
      if (timeA == null && timeB == null) {
        return 0;
      }
      if (timeA == null) {
        return 1;
      }
      if (timeB == null) {
        return -1;
      }
      return timeA.compareTo(timeB);
    });
    return histories;
  }

  Future<T> _guardApprovalAccess<T>(Future<T> Function() action) async {
    try {
      return await action();
    } on ApiException catch (error) {
      if (_isApprovalAccessDenied(error)) {
        throw ApprovalAccessDeniedException(
          message: _accessDeniedMessage(error),
          cause: error,
        );
      }
      rethrow;
    }
  }

  bool _isApprovalAccessDenied(ApiException error) {
    if (error.code != ApiErrorCode.forbidden) {
      return false;
    }
    final serverCode = _extractServerErrorCode(error);
    if (serverCode != null &&
        serverCode.toUpperCase() == 'APPROVAL_ACCESS_DENIED') {
      return true;
    }
    final message = error.message.trim().toLowerCase();
    if (message.contains('approval access denied')) {
      return true;
    }
    final reasons = error.details?.values
        .whereType<String>()
        .map((value) => value.toLowerCase())
        .toList(growable: false);
    if (reasons != null &&
        reasons.any((value) => value.contains('approval access denied'))) {
      return true;
    }
    return false;
  }

  String _accessDeniedMessage(ApiException error) {
    final message = error.message.trim();
    if (message.isNotEmpty) {
      return message;
    }
    final code = _extractServerErrorCode(error);
    if (code != null && code.isNotEmpty) {
      return '결재를 조회할 권한이 없습니다. (code: $code)';
    }
    return '결재를 조회할 권한이 없습니다. 관리자에게 권한을 요청하세요.';
  }

  String? _extractServerErrorCode(ApiException error) {
    final details = error.details;
    if (details != null) {
      final detailCode = details['code'] ?? details['error_code'];
      if (detailCode is String && detailCode.trim().isNotEmpty) {
        return detailCode.trim();
      }
    }
    final data = error.cause?.response?.data;
    return _readErrorCodeFromPayload(data);
  }

  String? _readErrorCodeFromPayload(dynamic data) {
    if (data is Map<String, dynamic>) {
      final direct = data['error_code'] ?? data['code'];
      if (direct is String && direct.trim().isNotEmpty) {
        return direct.trim();
      }
      final errorNode = data['error'];
      if (errorNode is Map<String, dynamic>) {
        final nested = errorNode['code'] ?? errorNode['error_code'];
        if (nested is String && nested.trim().isNotEmpty) {
          return nested.trim();
        }
      }
    }
    return null;
  }
}
