import type { InteractionLink as InteractionLinkType } from "./interactionLink.schema"
import type {
  FrontendPayload,
  FrontendPayloadRequestData,
} from "@c/payload/payloadTypes"
import { v4 as uuidv4, validate } from "uuid"
import { createInteractionLinkPayload } from "./helpers/payload"
import type { PluginFastifyInstance } from "types"
import type { Model } from "mongoose"

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

// InteractionLink Service class
export class InteractionLinkService {
  private interactionLinkModel: Model<InteractionLinkType>

  constructor(model: Model<InteractionLinkType>) {
    this.interactionLinkModel = model
  }

  /**
   * Core function to create an interaction link (returns raw data)
   */
  async _createInteractionLink(
    fastify: PluginFastifyInstance,
    interactionLinkBody: InteractionLinkBody,
    userId: string,
  ): Promise<InteractionLinkType> {
    try {
      if (interactionLinkBody._id) {
        const uuidValid = validate(interactionLinkBody._id)

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

        const duplicate = await this.interactionLinkModel.findOne({
          _id: interactionLinkBody._id,
        })

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

      const interactionLink = new this.interactionLinkModel({
        ...interactionLinkBody,
        author: userId,
        lastEditor: userId,
      })

      await interactionLink.save()

      fastify.log.info(`[API] Created interaction link #${interactionLink.id}`)

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

  /**
   * Create a new interaction link with payload wrapping and event emission
   */
  async createInteractionLink(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    interactionLinkBody: InteractionLinkBody,
    userId: string,
  ): Promise<FrontendPayload<InteractionLinkType[]>> {
    const interactionLink = await this._createInteractionLink(
      fastify,
      interactionLinkBody,
      userId,
    )

    const payload = await createInteractionLinkPayload(
      fastify,
      [interactionLink],
      requestData,
    )

    fastify.emit("interactionLinkCreated", payload)

    return payload
  }

  /**
   * Core function to get all interaction links (returns raw data)
   */
  async _readAllInteractionLinks(
    fastify: PluginFastifyInstance,
    filters: Record<string, any> = { isDeleted: false },
  ): Promise<InteractionLinkType[]> {
    try {
      filters = { isDeleted: false, ...filters }
      return await this.interactionLinkModel
        .find({
          ...filters,
        })
        .lean()
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Get all interaction links with payload wrapping
   */
  async getAllInteractionLinks(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    filters: Record<string, any> = {},
  ): Promise<FrontendPayload<InteractionLinkType[]>> {
    const interactionLinks = await this._readAllInteractionLinks(
      fastify,
      filters,
    )

    return createInteractionLinkPayload(fastify, interactionLinks, requestData)
  }

  /**
   * Core function to get interaction links by interactionLink ID (returns raw data)
   */
  async _readInteractionLinksByInteractionLink(
    fastify: PluginFastifyInstance,
    interactionLinkId: string,
  ): Promise<InteractionLinkType[]> {
    try {
      return await this.interactionLinkModel
        .find({
          interactionLink: interactionLinkId,
          isDeleted: false,
        })
        .lean()
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Get interaction links by interactionLink ID with payload wrapping
   */
  async readInteractionLinksByInteractionLink(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    interactionLinkId: string,
  ): Promise<FrontendPayload<InteractionLinkType[]>> {
    const interactionLinks = await this._readInteractionLinksByInteractionLink(
      fastify,
      interactionLinkId,
    )

    return createInteractionLinkPayload(fastify, interactionLinks, requestData)
  }

  /**
   * Core function to get a specific interaction link (returns raw data)
   */
  async _readInteractionLink(
    fastify: PluginFastifyInstance,
    id: string,
  ): Promise<InteractionLinkType> {
    try {
      const interactionLink = await this.interactionLinkModel
        .findOne({ _id: id })
        .lean()

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

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

  /**
   * Get a specific interaction link with payload wrapping
   */
  async readInteractionLink(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
  ): Promise<FrontendPayload<InteractionLinkType[]>> {
    const interactionLink = await this._readInteractionLink(fastify, id)

    return createInteractionLinkPayload(fastify, [interactionLink], requestData)
  }

  /**
   * Core function to update an interaction link (returns raw data)
   */
  async _updateInteractionLink(
    fastify: PluginFastifyInstance,
    id: string,
    interactionLinkBody: InteractionLinkBody,
    userId: string | undefined,
  ): Promise<{ updated: InteractionLinkType; original: InteractionLinkType }> {
    try {
      const originalInteractionLink = await this.interactionLinkModel.findOne({
        _id: id,
      })

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

      // Remove protected fields
      delete interactionLinkBody._id

      if (userId) {
        interactionLinkBody.lastEditor = userId
      }

      const updatedInteractionLink = await this.interactionLinkModel
        .findOneAndUpdate(
          { _id: id },
          {
            ...interactionLinkBody,
          },
          { new: true },
        )
        .lean()

      if (!updatedInteractionLink) {
        throw new Error(`Failed to update interaction link with ID ${id}`)
      }

      fastify.log.info(
        `[API] Updated interaction link #${updatedInteractionLink._id}`,
      )

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

  /**
   * Update an interaction link with payload wrapping and event emission
   */
  async updateInteractionLink(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
    interactionLinkBody: InteractionLinkBody,
  ): Promise<FrontendPayload<InteractionLinkType[]>> {
    const { updated, original } = await this._updateInteractionLink(
      fastify,
      id,
      interactionLinkBody,
      requestData.userId || "",
    )

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

    payload.body.prevVersion = original

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

    return payload
  }

  /**
   * Core function to soft delete an interaction link (returns raw data)
   */
  async _removeInteractionLink(
    fastify: PluginFastifyInstance,
    id: string,
  ): Promise<InteractionLinkType> {
    try {
      const interactionLink = await this.interactionLinkModel
        .findOneAndUpdate({ _id: id }, { isDeleted: true }, { new: true })
        .lean()

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

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

  /**
   * Soft delete an interaction link with payload wrapping and event emission
   */
  async removeInteractionLink(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
  ): Promise<FrontendPayload<InteractionLinkType[]>> {
    const interactionLink = await this._removeInteractionLink(fastify, id)

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

    payload.body.deleted = [interactionLink._id]
    fastify.emit("interactionLinkDeleted", payload)

    return payload
  }

  /**
   * Core function to hard delete an interaction link (returns raw data)
   */
  async _hardDeleteInteractionLink(
    fastify: PluginFastifyInstance,
    id: string,
  ): Promise<InteractionLinkType> {
    try {
      const interactionLink = await this.interactionLinkModel.findOne({
        _id: id,
        isDeleted: true,
      })

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

      const res = await interactionLink.deleteOne()

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

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

  /**
   * Hard delete an interaction link with payload wrapping and event emission
   */
  async hardDeleteInteractionLink(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
  ): Promise<FrontendPayload<InteractionLinkType[]>> {
    const interactionLink = await this._hardDeleteInteractionLink(fastify, id)

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

    payload.body.deleted = [interactionLink._id]
    fastify.emit("interactionLinkHardDeleted", payload)

    return payload
  }

  /**
   * Core function to get interaction links by trigger type and identifier (returns raw data)
   */
  async _readInteractionLinksByTrigger(
    fastify: PluginFastifyInstance,
    triggerType: string,
    identifier: string,
  ): Promise<InteractionLinkType[]> {
    try {
      return await this.interactionLinkModel
        .find({
          isDeleted: false,
          triggers: {
            $elemMatch: {
              type: triggerType,
              identifier: identifier,
            },
          },
        })
        .lean()
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Get interaction links by trigger type and identifier with payload wrapping
   */
  async readInteractionLinksByTrigger(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    triggerType: string,
    identifier: string,
  ): Promise<FrontendPayload<InteractionLinkType[]>> {
    const interactionLinks = await this._readInteractionLinksByTrigger(
      fastify,
      triggerType,
      identifier,
    )

    return createInteractionLinkPayload(fastify, interactionLinks, requestData)
  }
  /**
   * Core function to create multiple interactionLinks (returns raw data)
   */
  async _createInteractionLinks(
    fastify: PluginFastifyInstance,
    interactionLinkBodies: InteractionLinkBody[],
    userId: string,
  ): Promise<InteractionLinkType[]> {
    try {
      const interactionLinksToCreate: InteractionLinkBody[] = []

      // Validate and prepare all interactionLinks
      for (const interactionLinkBody of interactionLinkBodies) {
        if (interactionLinkBody._id) {
          const uuidValid = validate(interactionLinkBody._id)

          if (!uuidValid) {
            throw new Error(
              `Invalid ID - not a valid uuid v4: ${interactionLinkBody._id}`,
            )
          }

          const duplicate = await this.interactionLinkModel.findOne({
            _id: interactionLinkBody._id,
          })

          if (duplicate) {
            throw new Error(
              `An entry with this id already exists: ${interactionLinkBody._id}`,
            )
          }
        } else {
          interactionLinkBody._id = uuidv4()
        }

        interactionLinksToCreate.push({
          ...interactionLinkBody,
          author: userId,
          lastEditor: userId,
        })
      }

      // Bulk insert
      const interactionLinks = await this.interactionLinkModel.insertMany(
        interactionLinksToCreate,
      )

      fastify.log.info(
        `[API] Created ${interactionLinks.length} interactionLinks in bulk`,
      )

      return interactionLinks.map((comp) =>
        comp.toObject ? comp.toObject() : comp,
      )
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Create multiple interactionLinks with payload wrapping and event emission
   */
  async createInteractionLinks(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    interactionLinkBodies: InteractionLinkBody[],
    userId: string,
  ): Promise<FrontendPayload<InteractionLinkType[]>> {
    const interactionLinks = await this._createInteractionLinks(
      fastify,
      interactionLinkBodies,
      userId,
    )

    const payload = await createInteractionLinkPayload(
      fastify,
      interactionLinks,
      requestData,
    )

    fastify.emit("interactionLinksCreated", payload)

    return payload
  }
}

export const createInteractionLinkService = (
  model: Model<InteractionLinkType>,
) => new InteractionLinkService(model)
