import type { Account as AccountType } from "./account.schema"
import type {
  FrontendPayload,
  FrontendPayloadRequestData,
} from "@c/payload/payloadTypes"
import type { PluginFastifyInstance } from "types"
import { v4 as uuidv4, validate } from "uuid"
import { createAccountPayload } from "./helpers/payload"
import type { Model } from "mongoose"

export type AccountBody = Partial<Omit<AccountType, "_id">> & {
  _id?: string
}

export class AccountService {
  private accountModel: Model<AccountType>

  constructor(model: Model<AccountType>) {
    this.accountModel = model
  }

  /**
   * Core function to create an account (returns raw data)
   */
  async _createAccount(
    fastify: PluginFastifyInstance,
    accountBody: AccountBody,
    userId: string,
  ): Promise<AccountType> {
    try {
      if (accountBody._id) {
        const uuidValid = validate(accountBody._id)

        if (!uuidValid) {
          throw new Error("Invalid ID - not a valid uuid v4")
        }

        const duplicate = await this.accountModel.findById(accountBody._id)

        if (duplicate) {
          throw new Error("An entry with this id already exists")
        }
      } else {
        accountBody._id = uuidv4()
      }

      const account = new this.accountModel({
        ...accountBody,
        owner: userId,
      })

      await account.save()

      fastify.log.info(`[API] Created account #${account._id}`)

      return account.toObject ? account.toObject() : account
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Create a new account with payload wrapping and event emission
   */
  async createAccount(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    accountBody: AccountBody,
    userId: string,
  ): Promise<FrontendPayload<AccountType[]>> {
    const account = await this._createAccount(fastify, accountBody, userId)

    const payload = await createAccountPayload(fastify, [account], requestData)

    if (requestData.broadcast) {
      fastify.emit("accountCreated", payload)
    }

    return payload
  }

  /**
   * Core function to get all accounts (returns raw data)
   */
  async _readAccounts(fastify: PluginFastifyInstance): Promise<AccountType[]> {
    try {
      return await this.accountModel.find({ isDeleted: false }).lean()
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Get all accounts with payload wrapping
   */
  async readAccounts(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
  ): Promise<FrontendPayload<AccountType[]>> {
    const accounts = await this._readAccounts(fastify)

    return createAccountPayload(fastify, accounts, requestData)
  }

  /**
   * Core function to get a specific account (returns raw data)
   */
  async _readAccount(
    fastify: PluginFastifyInstance,
    id: string,
  ): Promise<AccountType> {
    try {
      const account = await this.accountModel.findById(id).lean()

      if (!account) {
        throw new Error(`Account with ID ${id} not found`)
      }

      return account
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Get a specific account with payload wrapping
   */
  async readAccount(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
  ): Promise<FrontendPayload<AccountType[]>> {
    const account = await this._readAccount(fastify, id)

    return createAccountPayload(fastify, [account], requestData)
  }

  /**
   * Core function to update an account (returns raw data)
   */
  async _updateAccount(
    fastify: PluginFastifyInstance,
    id: string,
    accountBody: AccountBody,
    userId: string,
  ): Promise<{ updated: AccountType; original: AccountType }> {
    try {
      const originalAccount = await this.accountModel.findById(id)

      if (!originalAccount) {
        throw new Error(`Account with ID ${id} not found`)
      }

      // Remove protected fields
      delete accountBody._id

      const updatedAccount = await this.accountModel
        .findByIdAndUpdate(
          id,
          {
            ...accountBody,
            updatedBy: userId,
          },
          { new: true },
        )
        .lean()

      if (!updatedAccount) {
        throw new Error(`Failed to update account with ID ${id}`)
      }

      fastify.log.info(`[API] Updated account #${updatedAccount._id}`)

      return {
        updated: updatedAccount,
        original: originalAccount.toObject
          ? originalAccount.toObject()
          : originalAccount,
      }
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Update an account with payload wrapping and event emission
   */
  async updateAccount(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
    accountBody: AccountBody,
    userId: string,
  ): Promise<FrontendPayload<AccountType[]>> {
    const { updated, original } = await this._updateAccount(
      fastify,
      id,
      accountBody,
      userId,
    )

    const payload = await createAccountPayload(fastify, [updated], requestData)

    payload.body.prevVersion = original

    if (requestData.broadcast) {
      fastify.emit("accountUpdated", payload)
    }

    return payload
  }

  /**
   * Core function to soft delete an account (returns raw data)
   */
  async _removeAccount(
    fastify: PluginFastifyInstance,
    id: string,
  ): Promise<AccountType> {
    try {
      const account = await this.accountModel
        .findByIdAndUpdate(id, { isDeleted: true }, { new: true })
        .lean()

      if (!account) {
        throw new Error(`Account with ID ${id} not found`)
      }

      return account
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Soft delete an account with payload wrapping and event emission
   */
  async removeAccount(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
  ): Promise<FrontendPayload<AccountType[]>> {
    const account = await this._removeAccount(fastify, id)

    const payload = await createAccountPayload(fastify, [account], {
      ...requestData,
      type: "dataDelete",
    })

    payload.body.deleted = [account._id]
    fastify.emit("accountDeleted", payload)

    return payload
  }

  /**
   * Core function to hard delete an account (returns raw data)
   */
  async _hardDeleteAccount(
    fastify: PluginFastifyInstance,
    id: string,
  ): Promise<AccountType> {
    try {
      const account = await this.accountModel.findOne({
        _id: id,
        isDeleted: true,
      })

      if (!account) {
        throw new Error(
          `Account with ID ${id} not found or not marked for deletion`,
        )
      }

      const res = await account.deleteOne()

      if (res?.deletedCount === 1) {
        return account.toObject ? account.toObject() : account
      }

      throw new Error(`Deletion of Account with ID ${id} was not successful.`)
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Hard delete an account with payload wrapping and event emission
   */
  async hardDeleteAccount(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
  ): Promise<FrontendPayload<AccountType[]>> {
    const account = await this._hardDeleteAccount(fastify, id)

    const payload = await createAccountPayload(fastify, [account], {
      ...requestData,
      type: "dataHardDeleted",
    })

    payload.body.deleted = [account._id]
    fastify.emit("accountHardDeleted", payload)

    return payload
  }

  /**
   * Core function to toggle block status of an account (returns raw data)
   */
  async _toggleBlockAccount(
    fastify: PluginFastifyInstance,
    id: string,
    blocked: boolean,
    userId: string,
  ): Promise<{ updated: AccountType; original: AccountType }> {
    try {
      const originalAccount = await this.accountModel.findById(id)

      if (!originalAccount) {
        throw new Error(`Account with ID ${id} not found`)
      }

      const updatedAccount = await this.accountModel
        .findByIdAndUpdate(
          id,
          {
            blocked,
            updatedBy: userId,
          },
          { new: true },
        )
        .lean()

      if (!updatedAccount) {
        throw new Error(`Failed to update account with ID ${id}`)
      }

      return {
        updated: updatedAccount,
        original: originalAccount.toObject
          ? originalAccount.toObject()
          : originalAccount,
      }
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Toggle block status of an account with payload wrapping and event emission
   */
  async toggleBlockAccount(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
    blocked: boolean,
    userId: string,
  ): Promise<FrontendPayload<AccountType[]>> {
    const { updated, original } = await this._toggleBlockAccount(
      fastify,
      id,
      blocked,
      userId,
    )

    const payload = await createAccountPayload(fastify, [updated], requestData)

    payload.body.prevVersion = original

    fastify.emit("accountUpdated", payload)

    return payload
  }
}

export const createAccountService = (model: Model<AccountType>) =>
  new AccountService(model)
