import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/common/utils/json_utils.dart';

import '../../domain/entities/approval.dart';
import 'approval_audit_dto.dart';
import 'approval_step_dto.dart';

/// 결재 API 응답을 표현하는 DTO.
///
/// - 원본 JSON 형식을 유지하면서 도메인 엔티티 변환을 제공한다.
/// - 일부 필드는 누락 가능성을 고려하여 기본값을 지정한다.
class ApprovalDto {
  ApprovalDto({
    this.id,
    required this.approvalNo,
    this.transactionId,
    this.transactionNo,
    this.transactionUpdatedAt,
    required this.status,
    this.currentStep,
    required this.requester,
    required this.requestedAt,
    this.decidedAt,
    this.note,
    this.isActive = true,
    this.isDeleted = false,
    this.steps = const [],
    this.histories = const [],
    this.createdAt,
    this.updatedAt,
  });

  final int? id;
  final String approvalNo;
  final int? transactionId;
  final String? transactionNo;
  final DateTime? transactionUpdatedAt;
  final ApprovalStatusDto status;
  final ApprovalStepDto? currentStep;
  final ApprovalRequesterDto requester;
  final DateTime requestedAt;
  final DateTime? decidedAt;
  final String? note;
  final bool isActive;
  final bool isDeleted;
  final List<ApprovalStepDto> steps;
  final List<ApprovalAuditDto> histories;
  final DateTime? createdAt;
  final DateTime? updatedAt;

  /// API 응답 JSON을 [ApprovalDto]로 변환한다.
  factory ApprovalDto.fromJson(Map<String, dynamic> json) {
    final approvalEnvelope = _mapOrEmpty(json['approval']);
    final statusMap = _firstNonEmptyMap([
      json['status'],
      json['approval_status'],
      approvalEnvelope['status'],
      approvalEnvelope['approval_status'],
    ]);
    final rawRequesterMap = _firstNonEmptyMap([
      json['requester'],
      json['requested_by'],
      approvalEnvelope['requester'],
      approvalEnvelope['requested_by'],
    ]);
    final currentStepMap = _firstNonEmptyMap([
      json['current_step'],
      json['currentStep'],
      approvalEnvelope['current_step'],
    ]);
    final transactionMap = _mapOrEmpty(json['transaction']);
    final envelopeTransactionMap = _mapOrEmpty(approvalEnvelope['transaction']);
    var stepsSource = _asListOfMap(json['steps']);
    if (stepsSource.isEmpty) {
      stepsSource = _asListOfMap(approvalEnvelope['steps']);
    }
    var historiesSource = _asListOfMap(json['histories']);
    if (historiesSource.isEmpty) {
      historiesSource = _asListOfMap(approvalEnvelope['histories']);
    }
    final currentStepDto = currentStepMap.isEmpty
        ? null
        : ApprovalStepDto.fromJson(currentStepMap);

    final approvalNo =
        _pickString(
          [json, approvalEnvelope],
          const ['approval_no', 'approvalNo'],
        ) ??
        '-';
    final transactionNo = _pickString(
      [json, transactionMap, approvalEnvelope, envelopeTransactionMap],
      const ['transaction_no', 'transactionNo'],
    );
    final transactionId =
        json['transaction_id'] as int? ??
        approvalEnvelope['transaction_id'] as int? ??
        transactionMap['id'] as int? ??
        envelopeTransactionMap['id'] as int?;
    final transactionUpdatedAt = _parseDate(
      transactionMap['updated_at'] ??
          envelopeTransactionMap['updated_at'] ??
          json['transaction_updated_at'] ??
          approvalEnvelope['transaction_updated_at'],
    );

    return ApprovalDto(
      id: json['id'] as int? ?? approvalEnvelope['id'] as int?,
      approvalNo: approvalNo,
      transactionId: transactionId,
      transactionNo: transactionNo,
      transactionUpdatedAt: transactionUpdatedAt,
      status: ApprovalStatusDto.fromJson(statusMap),
      currentStep: currentStepDto,
      requester: ApprovalRequesterDto.fromJson(
        _resolveRequesterMap(json, approvalEnvelope, rawRequesterMap),
      ),
      requestedAt:
          _parseDate(
            json['requested_at'] ?? approvalEnvelope['requested_at'],
          ) ??
          DateTime.now(),
      decidedAt: _parseDate(
        json['decided_at'] ?? approvalEnvelope['decided_at'],
      ),
      note: _readString(json['note']) ?? _readString(approvalEnvelope['note']),
      isActive:
          (json['is_active'] as bool?) ??
          (approvalEnvelope['is_active'] as bool?) ??
          true,
      isDeleted:
          (json['is_deleted'] as bool?) ??
          (approvalEnvelope['is_deleted'] as bool?) ??
          false,
      steps: stepsSource.map(ApprovalStepDto.fromJson).toList(growable: false),
      histories: historiesSource
          .map(ApprovalAuditDto.fromJson)
          .toList(growable: false),
      createdAt: _parseDate(
        json['created_at'] ?? approvalEnvelope['created_at'],
      ),
      updatedAt: _parseDate(
        json['updated_at'] ?? approvalEnvelope['updated_at'],
      ),
    );
  }

  /// DTO를 도메인 [Approval] 엔티티로 변환한다.
  Approval toEntity() => Approval(
    id: id,
    approvalNo: approvalNo,
    transactionId: transactionId,
    transactionNo: transactionNo ?? '-',
    transactionUpdatedAt: transactionUpdatedAt,
    status: status.toEntity(),
    currentStep: currentStep?.toEntity(),
    requester: requester.toEntity(),
    requestedAt: requestedAt,
    decidedAt: decidedAt,
    note: note,
    isActive: isActive,
    isDeleted: isDeleted,
    steps: steps.map((e) => e.toEntity()).toList(),
    histories: histories.map((e) => e.toEntity()).toList(),
    createdAt: createdAt,
    updatedAt: updatedAt,
  );

  /// 페이징 응답을 파싱해 [PaginatedResult]로 변환한다.
  static PaginatedResult<Approval> parsePaginated(Map<String, dynamic>? json) {
    final rawItems = JsonUtils.extractList(json, keys: const ['items']);
    final items = rawItems
        .map(ApprovalDto.fromJson)
        .map((dto) => dto.toEntity())
        .toList(growable: false);
    return PaginatedResult<Approval>(
      items: items,
      page: JsonUtils.readInt(json, 'page', fallback: 1),
      pageSize: JsonUtils.readInt(json, 'page_size', fallback: items.length),
      total: JsonUtils.readInt(json, 'total', fallback: items.length),
    );
  }
}

/// 결재 요청자 DTO.
class ApprovalRequesterDto {
  ApprovalRequesterDto({
    required this.id,
    required this.employeeNo,
    required this.name,
  });

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

  factory ApprovalRequesterDto.fromJson(Map<String, dynamic> json) {
    return ApprovalRequesterDto(
      id: JsonUtils.readInt(json, 'id', fallback: 0),
      employeeNo:
          _readString(json['employee_no']) ??
          _readString(json['employee_id']) ??
          '-',
      name:
          _readString(json['name']) ??
          _readString(json['employee_name']) ??
          '-',
    );
  }

  /// DTO를 [ApprovalRequester]로 변환한다.
  ApprovalRequester toEntity() =>
      ApprovalRequester(id: id, employeeNo: employeeNo, name: name);
}

List<Map<String, dynamic>> _asListOfMap(dynamic value) {
  if (value is List) {
    return value.whereType<Map<String, dynamic>>().toList(growable: false);
  }
  return const [];
}

Map<String, dynamic> _mapOrEmpty(dynamic value) =>
    value is Map<String, dynamic> ? value : const {};

Map<String, dynamic> _firstNonEmptyMap(List<dynamic> candidates) {
  for (final candidate in candidates) {
    if (candidate is Map<String, dynamic> && candidate.isNotEmpty) {
      return candidate;
    }
  }
  return const {};
}

Map<String, dynamic> _resolveRequesterMap(
  Map<String, dynamic> root,
  Map<String, dynamic> envelope,
  Map<String, dynamic> candidate,
) {
  if (candidate.isNotEmpty) {
    return candidate;
  }
  final resolved = <String, dynamic>{};
  final rootRequestedBy = _mapOrEmpty(root['requested_by']);
  if (rootRequestedBy.isNotEmpty) {
    resolved.addAll(rootRequestedBy);
  }
  final envelopeRequestedBy = _mapOrEmpty(envelope['requested_by']);
  if (resolved.isEmpty && envelopeRequestedBy.isNotEmpty) {
    resolved.addAll(envelopeRequestedBy);
  } else if (envelopeRequestedBy.isNotEmpty) {
    for (final entry in envelopeRequestedBy.entries) {
      resolved.putIfAbsent(entry.key, () => entry.value);
    }
  }

  final fallbackId = _pickInt(
    [resolved, rootRequestedBy, envelopeRequestedBy, root, envelope],
    const ['requester_id', 'requested_by_id', 'id'],
  );
  if (fallbackId != null) {
    resolved['id'] = fallbackId;
  }

  final fallbackEmployeeNo = _pickString(
    [resolved, rootRequestedBy, envelopeRequestedBy, root, envelope],
    const [
      'employee_no',
      'employee_id',
      'requester_employee_no',
      'requested_by_employee_no',
    ],
  );
  if (fallbackEmployeeNo != null) {
    resolved['employee_no'] = fallbackEmployeeNo;
  }

  final fallbackName = _pickString(
    [resolved, rootRequestedBy, envelopeRequestedBy, root, envelope],
    const ['name', 'employee_name', 'requester_name', 'requested_by_name'],
  );
  if (fallbackName != null) {
    resolved['name'] = fallbackName;
  }

  return resolved;
}

String? _pickString(List<dynamic> sources, List<String> keys) {
  for (final source in sources) {
    if (source is Map<String, dynamic>) {
      for (final key in keys) {
        final value = source[key];
        if (value is String && value.isNotEmpty) {
          return value;
        }
      }
    }
  }
  return null;
}

int? _pickInt(List<dynamic> sources, List<String> keys) {
  for (final source in sources) {
    if (source is Map<String, dynamic>) {
      for (final key in keys) {
        final value = source[key];
        if (value is int) {
          return value;
        }
        if (value is num) {
          return value.toInt();
        }
        if (value is String) {
          final parsed = int.tryParse(value);
          if (parsed != null) {
            return parsed;
          }
        }
      }
    }
  }
  return null;
}

/// 문자열/DateTime 입력을 DateTime으로 변환한다.
DateTime? _parseDate(Object? value) {
  if (value == null) return null;
  if (value is DateTime) return value;
  if (value is String) return DateTime.tryParse(value);
  return null;
}

String? _readString(dynamic value) {
  if (value == null) {
    return null;
  }
  if (value is String) {
    final trimmed = value.trim();
    return trimmed.isEmpty ? null : trimmed;
  }
  if (value is num || value is bool) {
    return value.toString();
  }
  return null;
}
