import type {
  WorkSessionCreate,
  WorkSession as WorkSessionType,
  WorkSessionUpdate,
} from "./workSession.schema"
import type {
  FrontendPayload,
  FrontendPayloadRequestData,
} from "@c/payload/payloadTypes"
import type { QueryOptions } from "@m/database/providers/mongodb/service"
import { v4 as uuidv4, validate } from "uuid"
import { createWorkSessionPayload } from "./helpers/payload"
import type { Model } from "mongoose"
import type { PluginFastifyInstance } from "types"
import { mergeDeepRight } from "ramda"

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

// Using a class to preserve 'this' context
export class WorkSessionService {
  private workSessionModel: Model<WorkSessionType>

  constructor(model: Model<WorkSessionType>) {
    this.workSessionModel = model
  }
  /**
   * Core function to create a new workSession (returns raw data)
   */
  async _createWorkSession(
    fastify: PluginFastifyInstance,
    workSessionBody: WorkSessionCreate,
  ): Promise<WorkSessionType> {
    try {
      if (workSessionBody._id) {
        const uuidValid = validate(workSessionBody._id)

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

        const duplicate = await this.workSessionModel.findById(
          workSessionBody._id,
        )

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

      const workSession = new this.workSessionModel(workSessionBody)

      await workSession.save()

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

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

  /**
   * Create a new workSession with payload wrapping and event emission
   */
  async createWorkSession(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    workSessionBody: WorkSessionCreate,
  ): Promise<FrontendPayload<WorkSessionType[]>> {
    const workSession = await this._createWorkSession(fastify, workSessionBody)

    const payload = await createWorkSessionPayload(
      fastify,
      [workSession],
      requestData,
    )

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

    return payload
  }

  /**
   * Core function to read all workSessions (returns raw data)
   */
  async _readWorkSessions(
    fastify: PluginFastifyInstance,
    filter: Record<string, any> = {},
    options: QueryOptions = {},
  ): Promise<WorkSessionType[]> {
    try {
      // Start building the query
      let query = this.workSessionModel.find(filter)

      // Apply pagination if provided
      if (options.limit !== undefined) {
        query = query.limit(options.limit)
      }
      if (options.offset !== undefined) {
        query = query.skip(options.offset)
      }

      // Apply population if provided
      if (options.populate && Array.isArray(options.populate)) {
        options.populate.forEach((populateOption) => {
          query = query.populate(populateOption as any)
        })
      }

      // Execute query
      return await query.lean()
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Read all workSessions with payload wrapping
   */
  async readWorkSessions(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    filter?: Record<string, any>,
    options: QueryOptions = {},
  ): Promise<FrontendPayload<WorkSessionType[]>> {
    const workSessions = await this._readWorkSessions(fastify, filter, options)

    return createWorkSessionPayload(fastify, workSessions, requestData)
  }

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

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

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

  /**
   * Get a specific workSession with payload wrapping
   */
  async readWorkSession(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
  ): Promise<FrontendPayload<WorkSessionType[]>> {
    const workSession = await this._readWorkSession(fastify, id)

    return createWorkSessionPayload(fastify, [workSession], requestData)
  }

  /**
   * Core function to update an existing workSession (returns raw data)
   */
  async _updateWorkSession(
    fastify: PluginFastifyInstance,
    id: string,
    workSessionBody: Partial<WorkSessionUpdate>,
    userId: string,
  ): Promise<{ updated: WorkSessionType; original: WorkSessionType }> {
    try {
      const originalWorkSession = await this.workSessionModel
        .findById(id)
        .lean()

      if (!originalWorkSession) {
        throw new Error(`WorkSession with ID ${id} not found`)
      }
      // Remove protected fields
      const mergedWorkSession = mergeDeepRight(
        originalWorkSession,
        workSessionBody,
      )
      const updatedWorkSession = await this.workSessionModel
        .findByIdAndUpdate(
          id,
          {
            pluginData: mergedWorkSession.pluginData,
            updatedBy: userId,
          },
          { new: true },
        )
        .lean()

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

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

      return {
        updated: updatedWorkSession,
        original: originalWorkSession,
      }
    } catch (error: any) {
      fastify.log.error(error.message)
      throw error
    }
  }

  /**
   * Update an existing workSession with payload wrapping and event emission
   */
  async updateWorkSession(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
    workSessionBody: Partial<WorkSessionUpdate>,
    userId: string,
  ): Promise<FrontendPayload<WorkSessionType[]>> {
    const { updated, original } = await this._updateWorkSession(
      fastify,
      id,
      workSessionBody,
      userId,
    )

    const payload = await createWorkSessionPayload(
      fastify,
      [updated],
      requestData,
    )
    payload.workSessionId = `session_${id}`
    payload.body.prevVersion = original

    if (requestData.broadcast) {
      // TODO Discuss if we want to send the full payload here or only the actual changed pluginData, or maybe even introduce a "workstate" object in the workSession document which get's broadcasted and keeps all the worksession relevant state data
      fastify.emit("workSessionUpdated", payload)
    }

    return payload
  }

  /**
   * Core function to soft delete a workSession (returns raw data)
   */
  async _removeWorkSession(
    fastify: PluginFastifyInstance,
    id: string,
  ): Promise<WorkSessionType> {
    try {
      const workSession = await this.workSessionModel
        .findByIdAndUpdate(id, { isActive: false }, { new: true })
        .lean()

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

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

  /**
   * Soft delete a workSession with payload wrapping and event emission
   */
  async removeWorkSession(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
  ): Promise<FrontendPayload<WorkSessionType[]>> {
    const workSession = await this._removeWorkSession(fastify, id)

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

    payload.body.deleted = [workSession._id]
    fastify.emit("workSessionDeleted", payload)

    return payload
  }

  /**
   * Core function to hard delete a workSession (returns raw data)
   */
  async _hardDeleteWorkSession(
    fastify: PluginFastifyInstance,
    id: string,
  ): Promise<WorkSessionType> {
    try {
      const workSession = await this.workSessionModel.findById(id)

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

      const res = await workSession.deleteOne()

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

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

  /**
   * Hard delete a workSession with payload wrapping and event emission
   */
  async hardDeleteWorkSession(
    fastify: PluginFastifyInstance,
    requestData: FrontendPayloadRequestData,
    id: string,
  ): Promise<FrontendPayload<WorkSessionType[]>> {
    const workSession = await this._hardDeleteWorkSession(fastify, id)

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

    payload.body.deleted = [workSession._id]
    fastify.emit("workSessionHardDeleted", payload)

    return payload
  }
}

export const createWorkSessionService = (model: Model<WorkSessionType>) =>
  new WorkSessionService(model)
