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');
    final permissionDtos = permissionList
        .map(AuthPermissionDto.fromJson)
        .toList(growable: true);
    final scopeCodes = _readScopeCodes(json);
    for (final scope in scopeCodes) {
      permissionDtos.add(AuthPermissionDto.fromScope(scope));
    }
    return AuthSessionDto(
      accessToken: token ?? '',
      refreshToken: refresh ?? '',
      expiresAt: expires,
      user: _parseUser(userMap),
      permissions: List<AuthPermissionDto>.unmodifiable(permissionDtos),
    );
  }

  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),
    );
  }

  factory AuthPermissionDto.fromScope(String scope) {
    final normalized = scope.trim();
    if (normalized.isEmpty) {
      throw const FormatException('권한 스코프 코드가 비어 있습니다.');
    }
    return AuthPermissionDto(resource: normalized, actions: const ['view']);
  }

  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 [];
}

Set<String> _readScopeCodes(Map<String, dynamic> source) {
  final codes = <String>{};

  void addCode(String? raw) {
    final normalized = raw == null ? null : _normalizeScopeCode(raw);
    if (normalized != null) {
      codes.add(normalized);
    }
  }

  void parse(dynamic value) {
    if (value == null) {
      return;
    }
    if (value is String) {
      addCode(value);
      return;
    }
    if (value is Iterable) {
      for (final item in value) {
        parse(item);
      }
      return;
    }
    if (value is Map<String, dynamic>) {
      for (final key in const [
        'scope_code',
        'scope',
        'code',
        'permission_code',
        'permission',
        'name',
        'scopeCode',
        'permissionCode',
        'scopeName',
      ]) {
        final candidate = value[key];
        if (candidate is String && candidate.trim().isNotEmpty) {
          addCode(candidate);
          return;
        }
      }
      for (final entry in value.entries) {
        final candidate = entry.value;
        if (candidate is String && candidate.trim().isNotEmpty) {
          addCode(candidate);
          return;
        }
      }
    }
  }

  parse(source['permission_codes']);
  parse(source['permission_scopes']);
  parse(source['group_permission_scopes']);

  return codes;
}

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;
}

String? _normalizeScopeCode(String value) {
  final trimmed = value.trim();
  if (trimmed.isEmpty) {
    return null;
  }
  final lowered = trimmed.toLowerCase();
  if (lowered.startsWith('scope:')) {
    return lowered;
  }
  return 'scope:$lowered';
}
