import 'dart:convert';

import 'package:crypto/crypto.dart';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:kreta_api/kreta_api.dart';
import 'package:firka/core/extensions.dart';
import 'package:firka_common/core/debug_helper.dart';
import 'package:isar_community/isar.dart';

part 'token_model.g.dart';

@collection
class TokenModel {
  Id? studentIdNorm; // Custom unique student identifier with "G0" removed
  String? studentId; // Custom unique student identifier
  String? iss; // Institution id for student
  String? idToken; // Unique identifier for the token if needed
  String? accessToken; // The main auth token
  String? refreshToken; // Token used to refresh the access token
  DateTime? expiryDate;
  int? tokenVersion;
  int? updatedAtMs;

  TokenModel();

  factory TokenModel.fromValues(
    Id studentIdNorm,
    studentId,
    String iss,
    String idToken,
    String accessToken,
    String refreshToken,
    int expiryDate, {
    int? tokenVersion,
    int? updatedAtMs,
  }) {
    var m = TokenModel();

    m.studentIdNorm = studentIdNorm;
    m.studentId = studentId;
    m.iss = iss;
    m.idToken = idToken;
    m.accessToken = accessToken;
    m.refreshToken = refreshToken;
    m.expiryDate = DateTime.fromMillisecondsSinceEpoch(expiryDate);
    m.tokenVersion = tokenVersion;
    m.updatedAtMs = updatedAtMs;

    return m;
  }

  factory TokenModel.fromResp(TokenGrantResponse resp) {
    var m = TokenModel();
    final jwt = JWT.decode(resp.idToken);

    final payload = jwt.payload as Map<String, dynamic>;
    final username = payload["kreta:user_name"].toString();
    if (username.isNumeric() ||
        (username.contains("G0") &&
            username.substring(0, username.length - 3).isNumeric())) {
      m.studentIdNorm = int.parse(username.toString().replaceAll("G0", ""));
    } else {
      // you would expect all usernames to be numeric
      // and for them be the student's student id, but NO
      final hash = sha256.convert(utf8.encode(username));
      final value =
          ((hash.bytes[0] << 24) |
              (hash.bytes[1] << 16) |
              (hash.bytes[2] << 8) |
              (hash.bytes[3])) >>>
          0;

      m.studentIdNorm = value & 0x3FFFFFFF;
    }
    m.studentId = payload["kreta:user_name"];
    m.iss = payload["kreta:institute_code"];
    m.idToken = resp.idToken;
    m.accessToken = resp.accessToken;
    m.refreshToken = resp.refreshToken;
    m.expiryDate = timeNow()
        .add(Duration(seconds: resp.expiresIn))
        .subtract(Duration(minutes: 1)); // just to be safe
    final iat = payload["iat"];
    if (iat is int) {
      m.tokenVersion = iat * 1000;
    } else if (iat is String) {
      final parsed = int.tryParse(iat);
      if (parsed != null) {
        m.tokenVersion = parsed * 1000;
      }
    }
    m.updatedAtMs = DateTime.now().millisecondsSinceEpoch;

    return m;
  }
}
