import { v4 as uuidv4 } from "uuid";
import { DataSource } from "typeorm";
import { User } from "../../entity/user.entity";
import { Account } from "../../entity/account.entity";
import { ADMIN_ROLES, USER_ROLES } from "../../constants";
import { License, LicenseAllocationHistory } from "../../entity/license.entity";

type InitialTestDBState = {
  tier1Accounts: { account: Account; users: User[] }[];
  tier2Accounts: { account: Account; users: User[] }[];
  tier3Accounts: { account: Account; users: User[] }[];
  tier4Accounts: { account: Account; users: User[] }[];
  tier5Accounts: { account: Account; users: User[] }[];
};

// 上書きされたら困る項目を除外したAccount型
type OverrideAccount = Omit<
  Account,
  "id" | "primary_admin_user_id" | "secondary_admin_user_id" | "user"
>;

// 上書きされたら困る項目を除外したUser型
type OverrideUser = Omit<
  User,
  "id" | "account" | "license" | "userGroupMembers"
>;

type AccountDefault = { [K in keyof OverrideAccount]?: OverrideAccount[K] };
type UserDefault = { [K in keyof OverrideUser]?: OverrideUser[K] };

/**
 * テスト ユーティリティ: 指定したプロパティを上書きしたユーザーを作成する
 * @param dataSource データソース
 * @param defaultUserValue User型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト
 * @returns 作成したユーザー
 */
export const makeTestUser = async (
  datasource: DataSource,
  defaultUserValue?: UserDefault
): Promise<User> => {
  const d = defaultUserValue;
  const { identifiers } = await datasource.getRepository(User).insert({
    account_id: d?.account_id ?? -1,
    external_id: d?.external_id ?? uuidv4(),
    role: d?.role ?? `${ADMIN_ROLES.STANDARD} ${USER_ROLES.NONE}`,
    author_id: d?.author_id,
    accepted_eula_version: d?.accepted_eula_version ?? "1.0",
    accepted_dpa_version: d?.accepted_dpa_version ?? "1.0",
    email_verified: d?.email_verified ?? true,
    auto_renew: d?.auto_renew ?? true,
    license_alert: d?.license_alert ?? true,
    notification: d?.notification ?? true,
    encryption: d?.encryption ?? true,
    encryption_password: d?.encryption_password,
    prompt: d?.prompt ?? true,
    created_by: d?.created_by ?? "test_runner",
    created_at: d?.created_at ?? new Date(),
    updated_by: d?.updated_by ?? "updater",
    updated_at: d?.updated_at ?? new Date(),
  });
  const result = identifiers.pop() as User;

  const user = await datasource.getRepository(User).findOne({
    where: {
      id: result.id,
    },
  });
  if (!user) {
    throw new Error("Unexpected null");
  }
  return user;
};

/**
 * テスト ユーティリティ: 指定したプロパティを上書きしたアカウントとその管理者ユーザーを作成する
 * @param dataSource データソース
 * @param defaultUserValue Account型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト
 * @param defaultAdminUserValue User型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト(account_id等の所属関係が破壊される上書きは無視する)
 * @returns 作成したアカウント
 */
export const makeTestAccount = async (
  datasource: DataSource,
  defaultAccountValue?: AccountDefault,
  defaultAdminUserValue?: UserDefault,
  isPrimaryAdminNotExist?: boolean,
  isSecondaryAdminNotExist?: boolean
): Promise<{ account: Account; admin: User }> => {
  let accountId: number;
  let userId: number;
  {
    const d = defaultAccountValue;
    const { identifiers } = await datasource.getRepository(Account).insert({
      tier: d?.tier ?? 1,
      parent_account_id: d?.parent_account_id ?? undefined,
      country: d?.country ?? "US",
      delegation_permission: d?.delegation_permission ?? false,
      locked: d?.locked ?? false,
      company_name: d?.company_name ?? "test inc.",
      verified: d?.verified ?? true,
      deleted_at: d?.deleted_at ?? "",
      created_by: d?.created_by ?? "test_runner",
      created_at: d?.created_at ?? new Date(),
      updated_by: d?.updated_by ?? "updater",
      updated_at: d?.updated_at ?? new Date(),
    });
    const result = identifiers.pop() as Account;
    accountId = result.id;
  }
  {
    const d = defaultAdminUserValue;
    const { identifiers } = await datasource.getRepository(User).insert({
      external_id: d?.external_id ?? uuidv4(),
      account_id: accountId,
      role: d?.role ?? "admin none",
      author_id: d?.author_id ?? undefined,
      accepted_eula_version: d?.accepted_eula_version ?? "1.0",
      accepted_dpa_version: d?.accepted_dpa_version ?? "1.0",
      email_verified: d?.email_verified ?? true,
      auto_renew: d?.auto_renew ?? true,
      license_alert: d?.license_alert ?? true,
      notification: d?.notification ?? true,
      encryption: d?.encryption ?? true,
      encryption_password: d?.encryption_password ?? "password",
      prompt: d?.prompt ?? true,
      deleted_at: d?.deleted_at ?? "",
      created_by: d?.created_by ?? "test_runner",
      created_at: d?.created_at ?? new Date(),
      updated_by: d?.updated_by ?? "updater",
      updated_at: d?.updated_at ?? new Date(),
    });

    const result = identifiers.pop() as User;
    userId = result.id;
  }

  // Accountの管理者を設定する
  let secondaryAdminUserId: number | null = null;
  if (isPrimaryAdminNotExist && !isSecondaryAdminNotExist) {
    secondaryAdminUserId = userId;
  }
  await datasource.getRepository(Account).update(
    { id: accountId },
    {
      primary_admin_user_id: isPrimaryAdminNotExist ? null : userId,
      secondary_admin_user_id: secondaryAdminUserId,
    }
  );

  const account = await datasource.getRepository(Account).findOne({
    where: {
      id: accountId,
    },
  });

  const admin = await datasource.getRepository(User).findOne({
    where: {
      id: userId,
    },
  });
  if (!account || !admin) {
    throw new Error("Unexpected null");
  }

  return {
    account: account,
    admin: admin,
  };
};

export const createLicense = async (
  datasource: DataSource,
  licenseId: number,
  expiry_date: Date | null,
  accountId: number,
  type: string,
  status: string,
  allocated_user_id: number | null,
  order_id: number | null,
  deleted_at: Date | null,
  delete_order_id: number | null
): Promise<void> => {
  const { identifiers } = await datasource.getRepository(License).insert({
    id: licenseId,
    expiry_date: expiry_date,
    account_id: accountId,
    type: type,
    status: status,
    allocated_user_id: allocated_user_id,
    order_id: order_id,
    deleted_at: deleted_at,
    delete_order_id: delete_order_id,
    created_by: "test_runner",
    created_at: new Date(),
    updated_by: "updater",
    updated_at: new Date(),
  });
  identifiers.pop() as License;
};

export const selectLicenseByAllocatedUser = async (
  datasource: DataSource,
  userId: number
): Promise<{ license: License | null }> => {
  const license = await datasource.getRepository(License).findOne({
    where: {
      allocated_user_id: userId,
    },
  });
  return { license };
};

export const selectLicenseAllocationHistory = async (
  datasource: DataSource,
  userId: number,
  licence_id: number
): Promise<{ licenseAllocationHistory: LicenseAllocationHistory | null }> => {
  const licenseAllocationHistory = await datasource
    .getRepository(LicenseAllocationHistory)
    .findOne({
      where: {
        user_id: userId,
        license_id: licence_id,
      },
      order: {
        executed_at: "DESC",
      },
    });
  return { licenseAllocationHistory };
};
