import '../../../../core/common/utils/json_utils.dart';
import '../../domain/entities/auth_permission.dart';
import '../../domain/entities/auth_session.dart';
import '../../domain/entities/authenticated_user.dart';

/// 로그인/토큰 갱신 응답을 역직렬화하는 DTO.
class AuthSessionDto {
  AuthSessionDto({
    required this.accessToken,
    required this.refreshToken,
    required this.user,
    this.expiresAt,
    this.permissions = const [],
  });

  final String accessToken;
  final String refreshToken;
  final DateTime? expiresAt;
  final AuthenticatedUser user;
  final List<AuthPermissionDto> permissions;

  factory AuthSessionDto.fromJson(Map<String, dynamic> json) {
    final token = _readString(json, 'access_token');
    final refresh = _readString(json, 'refresh_token');
    final expires = _parseDate(_readString(json, 'expires_at'));
    final userMap = _readMap(json, 'user');
    final permissionList = _readList(json, 'permissions');
    return AuthSessionDto(
      accessToken: token ?? '',
      refreshToken: refresh ?? '',
      expiresAt: expires,
      user: _parseUser(userMap),
      permissions: permissionList
          .map(AuthPermissionDto.fromJson)
          .toList(growable: false),
    );
  }

  AuthSession toEntity() {
    return AuthSession(
      accessToken: accessToken,
      refreshToken: refreshToken,
      expiresAt: expiresAt,
      user: user,
      permissions: permissions
          .map((dto) => dto.toEntity())
          .toList(growable: false),
    );
  }
}

class AuthPermissionDto {
  const AuthPermissionDto({required this.resource, required this.actions});

  final String resource;
  final List<String> actions;

  factory AuthPermissionDto.fromJson(Map<String, dynamic>? json) {
    if (json == null) {
      throw const FormatException('권한 정보가 비어 있습니다.');
    }
    final resource = _readString(json, 'resource') ?? '';
    final actions = <String>[];
    final rawActions = json['actions'];
    if (rawActions is List) {
      for (final item in rawActions) {
        if (item is String) {
          final normalized = item.trim().toLowerCase();
          if (normalized.isNotEmpty) {
            actions.add(normalized);
          }
          continue;
        }
        if (item is Map<String, dynamic>) {
          for (final entry in item.entries) {
            final normalized = entry.value.toString().trim().toLowerCase();
            if (normalized.isNotEmpty) {
              actions.add(normalized);
            }
          }
        }
      }
    }
    return AuthPermissionDto(
      resource: resource,
      actions: actions.toList(growable: false),
    );
  }

  AuthPermission toEntity() =>
      AuthPermission(resource: resource, actions: actions);
}

AuthenticatedUser _parseUser(Map<String, dynamic> json) {
  final id = _readOptionalInt(json, 'id') ?? 0;
  final name = _readString(json, 'name') ?? '';
  final employeeNo = _readString(json, 'employee_no');
  final email = _readString(json, 'email');
  final phone = _readString(json, 'phone');
  final group = JsonUtils.extractMap(
    json,
    keys: const ['group', 'primary_group'],
  );
  return AuthenticatedUser(
    id: id,
    name: name,
    employeeNo: employeeNo,
    email: email,
    phone: phone,
    primaryGroupId: _readOptionalInt(group, 'id'),
    primaryGroupName: _readString(group, 'name'),
  );
}

String? _readString(Map<String, dynamic>? source, String key) {
  if (source == null) {
    return null;
  }
  final value = source[key];
  if (value is String) {
    return value.trim();
  }
  return null;
}

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

Map<String, dynamic> _readMap(Map<String, dynamic> source, String key) {
  final value = source[key];
  if (value is Map<String, dynamic>) {
    return value;
  }
  return const {};
}

DateTime? _parseDate(String? value) {
  if (value == null || value.isEmpty) {
    return null;
  }
  return DateTime.tryParse(value);
}

int? _readOptionalInt(Map<String, dynamic>? source, String key) {
  if (source == null) {
    return null;
  }
  final value = source[key];
  if (value is int) {
    return value;
  }
  if (value is String) {
    return int.tryParse(value);
  }
  if (value is double) {
    return value.round();
  }
  return null;
}
