import { z } from "zod";
import { CredentialType, Credential } from "../../generated/prisma/client";
import { prisma } from "../constants";
import { CredentialTypeField } from "../modules/credentials/credentialTypeDefs";
import { CredentialController } from "./CredentialController";

/**
 * Credential Type Controller
 *
 * This class manages credential type data, including field definitions,
 * permission scopes, and associated credentials.
 */
export class CredentialTypeController {
  public readonly id: string;
  public name: string;
  public permissionScope: string;
  public icon: string | null;
  public fields: CredentialTypeField[];

  private _credentials: Credential[];

  public readonly createdAt: Date;
  public updatedAt: Date;

  constructor(
    credentialTypeData: CredentialType & {
      credentials: Credential[];
    },
  ) {
    this.id = credentialTypeData.id;
    this.name = credentialTypeData.name;
    this.permissionScope = credentialTypeData.permissionScope;
    this.icon = credentialTypeData.icon;
    this.fields = credentialTypeData.fields! as any as CredentialTypeField[];
    this._credentials = credentialTypeData.credentials;
    this.createdAt = credentialTypeData.createdAt;
    this.updatedAt = credentialTypeData.updatedAt;
  }

  /**
   * Update Internal Values
   *
   * Internal method to update all internal values when we query the database.
   * This keeps everything up-to-date even when we pass around the credential type controller.
   *
   * @param credentialTypeData - CredentialType object from Prisma
   */
  private _updateInternalValues(credentialTypeData: CredentialType) {
    this.name = credentialTypeData.name;
    this.permissionScope = credentialTypeData.permissionScope;
    this.icon = credentialTypeData.icon;
    this.fields = credentialTypeData.fields! as any as CredentialTypeField[];
    this.updatedAt = credentialTypeData.updatedAt;
  }

  /**
   * Update Credential Type
   *
   * Update the credential type's name, permission scope, icon, or fields.
   *
   * @param data - Partial credential type data to update
   * @returns {Promise<CredentialTypeController>} - The updated credential type controller
   */
  async update(
    data: Partial<
      Pick<CredentialType, "name" | "permissionScope" | "icon"> & {
        fields: CredentialTypeField[];
      }
    >,
  ): Promise<CredentialTypeController> {
    const pData = z
      .object({
        name: z.string().optional(),
        permissionScope: z.string().optional(),
        icon: z.string().optional(),
        fields: z.array(z.any()).optional(),
      })
      .strict()
      .parse(data);

    const updatedCredentialType = await prisma.credentialType.update({
      where: { id: this.id },
      data: pData,
    });

    this._updateInternalValues(updatedCredentialType);

    return this;
  }

  /**
   * Get Field Definition
   *
   * Get the definition for a specific field by its ID.
   *
   * @param fieldId - The field ID to look up
   * @returns {CredentialTypeField | undefined} - The field definition or undefined
   */
  getFieldDefinition(fieldId: string): CredentialTypeField | undefined {
    return this.fields.find((f) => f.id === fieldId);
  }

  /**
   * Get Required Fields
   *
   * Returns all fields that are marked as required.
   *
   * @returns {CredentialTypeField[]} - Array of required fields
   */
  getRequiredFields(): CredentialTypeField[] {
    return this.fields.filter((f) => f.required);
  }

  /**
   * Get Secure Fields
   *
   * Returns all fields that should be stored securely (encrypted).
   *
   * @returns {CredentialTypeField[]} - Array of secure fields
   */
  getSecureFields(): CredentialTypeField[] {
    return this.fields.filter((f) => f.secure);
  }

  /**
   * Fetch Credentials
   *
   * Fetch all credentials that use this credential type.
   *
   * @returns {Promise<CredentialController[]>} - Array of credential controllers
   */
  async fetchCredentials(): Promise<CredentialController[]> {
    const credentials = await prisma.credential.findMany({
      where: { typeId: this.id },
      include: {
        type: true,
        company: true,
        securevalues: true,
      },
    });

    return credentials.map((cred) => new CredentialController(cred));
  }

  /**
   * Count Credentials
   *
   * Count how many credentials use this credential type.
   *
   * @returns {number} - Number of credentials using this type
   */
  countCredentials(): number {
    return this._credentials.length;
  }

  /**
   * To JSON
   *
   * Create an object that can be safely returned to the user.
   *
   * @param opts - Options to change the output
   * @returns - An object that is JSON friendly
   */
  toJson(opts?: { includeCredentialCount?: boolean }) {
    return {
      id: this.id,
      name: this.name,
      permissionScope: this.permissionScope,
      icon: this.icon,
      fields: this.fields,
      credentialCount: opts?.includeCredentialCount
        ? this._credentials.length
        : undefined,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt,
    };
  }
}
