import 'dart:math';

import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/ci_string.dart';
import 'package:nc_photos/string_extension.dart';
import 'package:nc_photos/type.dart';

/// Details of a remote Nextcloud server account
class Account with EquatableMixin {
  Account(
    this.id,
    this.scheme,
    String address,
    this.userId,
    this.username2,
    this.password,
    List<String> roots,
  )   : address = address.trimRightAny("/"),
        _roots = roots.map((e) => e.trimRightAny("/")).toList() {
    if (scheme != "http" && scheme != "https") {
      throw const FormatException("scheme is neither http or https");
    }
  }

  Account copyWith({
    String? id,
    String? scheme,
    String? address,
    CiString? userId,
    String? username2,
    String? password,
    List<String>? roots,
  }) {
    return Account(
      id ?? this.id,
      scheme ?? this.scheme,
      address ?? this.address,
      userId ?? this.userId,
      username2 ?? this.username2,
      password ?? this.password,
      roots ?? List.of(_roots),
    );
  }

  static String newId() {
    final timestamp = DateTime.now().millisecondsSinceEpoch;
    final random = Random().nextInt(0xFFFFFF);
    return "${timestamp.toRadixString(16)}-${random.toRadixString(16).padLeft(6, '0')}";
  }

  @override
  toString() {
    return "$runtimeType {"
        "id: '$id', "
        "scheme: '$scheme', "
        "address: '${kDebugMode ? address : "***"}', "
        "userId: '${kDebugMode ? userId : "***"}', "
        "username2: '${kDebugMode ? username2 : "***"}', "
        "password: '${password.isNotEmpty == true ? (kDebugMode ? password : '***') : null}', "
        "roots: List {'${roots.join('\', \'')}'}, "
        "}";
  }

  static Account? fromJson(
    JsonObj json, {
    required AccountUpgraderV1? upgraderV1,
  }) {
    final jsonVersion = json["version"] ?? 1;
    JsonObj? result = json;
    if (jsonVersion < 2) {
      result = upgraderV1?.call(result);
      if (result == null) {
        _log.info("[fromJson] Version $jsonVersion not compatible");
        return null;
      }
    }
    return Account(
      result["id"],
      result["scheme"],
      result["address"],
      CiString(result["userId"]),
      result["username2"],
      result["password"],
      result["roots"].cast<String>(),
    );
  }

  JsonObj toJson() => {
        "version": version,
        "id": id,
        "scheme": scheme,
        "address": address,
        "userId": userId.toString(),
        "username2": username2,
        "password": password,
        "roots": _roots,
      };

  @override
  get props => [id, scheme, address, userId, username2, password, _roots];

  List<String> get roots => _roots;

  final String id;
  final String scheme;
  final String address;
  // For non LDAP users, this is the username used to sign in
  final CiString userId;
  // Username used to sign in. For non-LDAP users, this is identical to userId
  final String username2;
  final String password;

  final List<String> _roots;

  /// versioning of this class, use to upgrade old persisted accounts
  static const version = 2;

  static final _log = Logger("account.Account");
}

extension AccountExtension on Account {
  String get url => "$scheme://$address";

  /// Compare the server identity of two Accounts
  ///
  /// Return true if two Accounts point to the same user on server. Be careful
  /// that this does NOT mean that the two Accounts are identical (e.g., they
  /// can have difference password)
  bool compareServerIdentity(Account other) {
    return scheme == other.scheme &&
        address == other.address &&
        userId == other.userId;
  }
}

abstract class AccountUpgrader {
  JsonObj? call(JsonObj json);
}

class AccountUpgraderV1 implements AccountUpgrader {
  const AccountUpgraderV1({
    this.logAccountId,
  });

  @override
  call(JsonObj json) {
    // clarify user id and display name v1
    _log.fine("[call] Upgrade v1 Account: $logAccountId");
    final result = JsonObj.of(json);
    result["userId"] = json["altHomeDir"] ?? json["username"];
    result["username2"] = json["username"];
    result
      ..remove("username")
      ..remove("altHomeDir");
    return result;
  }

  /// Account ID for logging only
  final String? logAccountId;

  static final _log = Logger("account.AccountUpgraderV1");
}