import 'dart:typed_data';

import 'package:crypto/crypto.dart';

/// 세이브 파일 HMAC-SHA256 무결성(integrity) 검증 유틸리티.
///
/// 파일 포맷: [32-byte HMAC][GZip data]
/// 구 포맷(legacy): [GZip data] (HMAC 없음, GZip 매직 바이트 0x1f 0x8b로 시작)
class SaveIntegrity {
  SaveIntegrity._();

  /// HMAC-SHA256 출력 길이 (bytes)
  static const int hmacLength = 32;

  /// GZip 매직 바이트 (magic bytes) — 구 포맷 판별용
  static const int _gzipMagic1 = 0x1f;
  static const int _gzipMagic2 = 0x8b;

  /// 난독화(obfuscation)된 HMAC 키 생성.
  /// 소스에 평문(plaintext)으로 저장하지 않기 위해 XOR 분할.
  static List<int> get _hmacKey {
    // 파트 A: 원본 키의 전반부
    const partA = <int>[
      0x41, 0x73, 0x63, 0x69, 0x69, 0x4e, 0x65, 0x76,
      0x65, 0x72, 0x44, 0x69, 0x65, 0x53, 0x61, 0x76,
    ];
    // 파트 B: XOR 마스크(mask)
    const mask = <int>[
      0x7a, 0x1c, 0x0f, 0x05, 0x0d, 0x22, 0x09, 0x1a,
      0x09, 0x1e, 0x28, 0x05, 0x09, 0x3f, 0x0d, 0x1a,
    ];
    // 파트 C: partA XOR mask 결과 (키 후반부)
    const partC = <int>[
      0x3b, 0x6f, 0x6c, 0x6c, 0x64, 0x6c, 0x6c, 0x6c,
      0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c,
    ];

    // 전반부(partA) + 후반부(partC XOR mask)로 32바이트 키 생성
    final key = List<int>.filled(32, 0);
    for (var i = 0; i < 16; i++) {
      key[i] = partA[i];
      key[i + 16] = partC[i] ^ mask[i];
    }
    return key;
  }

  /// GZip 데이터에 HMAC-SHA256 서명(signature) 추가.
  /// 반환: [32-byte HMAC][gzipBytes]
  static Uint8List sign(List<int> gzipBytes) {
    final mac = _computeHmac(gzipBytes);
    final result = Uint8List(hmacLength + gzipBytes.length);
    result.setRange(0, hmacLength, mac.bytes);
    result.setRange(hmacLength, result.length, gzipBytes);
    return result;
  }

  /// 파일 바이트에서 HMAC를 검증(verify)하고 GZip 데이터를 반환.
  ///
  /// - HMAC 검증 성공: GZip 바이트 반환
  /// - 구 포맷(legacy, HMAC 없음): GZip 바이트 그대로 반환 + [isLegacy] = true
  /// - HMAC 검증 실패: [SaveIntegrityException] 발생
  static SaveIntegrityResult verify(List<int> fileBytes) {
    if (_isLegacyFormat(fileBytes)) {
      return SaveIntegrityResult(
        gzipBytes: Uint8List.fromList(fileBytes),
        isLegacy: true,
      );
    }

    if (fileBytes.length < hmacLength) {
      throw const SaveIntegrityException('파일이 너무 작습니다');
    }

    final storedHmac = fileBytes.sublist(0, hmacLength);
    final gzipBytes = fileBytes.sublist(hmacLength);
    final computed = _computeHmac(gzipBytes);

    // 상수 시간(constant-time) 비교로 타이밍 공격(timing attack) 방지
    var match = true;
    for (var i = 0; i < hmacLength; i++) {
      if (storedHmac[i] != computed.bytes[i]) {
        match = false;
      }
    }

    if (!match) {
      throw const SaveIntegrityException('세이브 파일 무결성 검증 실패');
    }

    return SaveIntegrityResult(
      gzipBytes: Uint8List.fromList(gzipBytes),
      isLegacy: false,
    );
  }

  /// 구 포맷 판별: GZip 매직 바이트(0x1f 0x8b)로 시작하면 HMAC 없는 레거시
  static bool _isLegacyFormat(List<int> bytes) {
    if (bytes.length < 2) return false;
    return bytes[0] == _gzipMagic1 && bytes[1] == _gzipMagic2;
  }

  static Digest _computeHmac(List<int> data) {
    final hmac = Hmac(sha256, _hmacKey);
    return hmac.convert(data);
  }
}

/// HMAC 검증 결과(result)
class SaveIntegrityResult {
  const SaveIntegrityResult({
    required this.gzipBytes,
    required this.isLegacy,
  });

  /// HMAC을 제외한 순수 GZip 데이터
  final Uint8List gzipBytes;

  /// 구 포맷(legacy) 여부 — true면 HMAC 없이 로드됨
  final bool isLegacy;
}

/// 세이브 파일 무결성 검증 실패 예외(exception)
class SaveIntegrityException implements Exception {
  const SaveIntegrityException(this.message);
  final String message;

  @override
  String toString() => 'SaveIntegrityException: $message';
}
