import { Injectable } from "@nestjs/common"
import { CreateLeaveTypeDto } from "./dto/create-leave-type.dto"
import { UpdateLeaveTypeDto } from "./dto/update-leave-type.dto"
import { LeaveTypeRepository } from "./repositories/leave-type.repository"
import {
  errorMessage,
  isEmpty,
  successMessage,
  validationMessage,
} from "../../utils/helpers"
import {
  failureResponse,
  successResponse,
} from "../../common/response/response"
import { code } from "../../common/response/response.code"
import { messageKey } from "../../constants/message-keys"
import { LeaveType } from "./entities/leave-type.entity"
import { verifyJwtToken } from "src/utils/jwt"

@Injectable()
export class LeaveTypesService {
  constructor(private readonly leaveTypeRepository: LeaveTypeRepository) {}

  async create(createLeaveTypeDto: CreateLeaveTypeDto, token: string) {
    const decoded = verifyJwtToken(token)
    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    // Check if leave type with same code exists for the company
    const existingLeaveType = await this.leaveTypeRepository.getByParams({
      where: {
        company_id: decoded.company_id,
        code: createLeaveTypeDto.code,
      },
      findOne: true,
    })

    if (existingLeaveType) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.already_exist, {
          ":data": "Leave Type",
          ":field": "code",
        }),
      )
    }

    // Validate parent_id if provided
    let level = 0
    if (createLeaveTypeDto.parent_id) {
      const parentLeaveType: any = await this.leaveTypeRepository.getByParams({
        where: {
          id: createLeaveTypeDto.parent_id,
          company_id: decoded.company_id,
        },
        findOne: true,
      })

      if (!parentLeaveType) {
        return failureResponse(
          code.VALIDATION,
          errorMessage(messageKey.data_not_found, {
            ":data": "Parent Leave Type",
          }),
        )
      }

      // Ensure we only allow 2 levels (main -> sub)
      if (parentLeaveType.level >= 1) {
        return failureResponse(
          code.VALIDATION,
          validationMessage(
            "Sub leave types can only be created under main leave types",
          ),
        )
      }

      level = parentLeaveType.level + 1

      // Sub leave types should not have annual_allowed_days
      if (
        createLeaveTypeDto.annual_allowed_days &&
        createLeaveTypeDto.annual_allowed_days > 0
      ) {
        return failureResponse(
          code.VALIDATION,
          validationMessage("Sub leave types cannot have annual allowed days"),
        )
      }
    }

    let leaveType: any = new LeaveType()
    leaveType = {
      ...createLeaveTypeDto,
      status: createLeaveTypeDto.status || 1,
      is_paid: createLeaveTypeDto.is_paid,
      carry_forward: createLeaveTypeDto.carry_forward,
      company_id: decoded.company_id,
      created_by: decoded.user_id,
      parent_id: createLeaveTypeDto.parent_id || null,
      level: level,
      annual_allowed_days: createLeaveTypeDto.parent_id
        ? 0
        : createLeaveTypeDto.annual_allowed_days || 0,
    }

    await this.leaveTypeRepository.save(leaveType)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_add, { ":data": "Leave Type" }),
    )
  }

  async findAll(query: any = {}, token: string) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }
    const {
      page = 1,
      limit = 10,
      search,
      status,
      is_paid,
      column_name,
      order = "DESC",
      parent_id,
      level,
      selectable_only,
      hierarchy = false,
    } = query

    // Handle different response formats based on query parameters
    if (hierarchy === true || hierarchy === "true") {
      return this.getHierarchy(token, { page, limit, search, status, is_paid })
    }

    if (selectable_only === true || selectable_only === "true") {
      return this.getSelectableLeaveTypes(token, {
        page,
        limit,
        search,
        status,
        is_paid,
      })
    }

    if (parent_id !== undefined) {
      return this.getSubLeaveTypes(parseInt(parent_id), token, {
        page,
        limit,
        search,
        status,
        is_paid,
      })
    }

    const skip = (page - 1) * limit
    const take = parseInt(limit)

    const orderDirection = isEmpty(order) ? "DESC" : order.toUpperCase()

    // Set up dynamic ordering
    let orderBy: any = { created_at: orderDirection }

    if (!isEmpty(column_name)) {
      switch (column_name) {
        case "name":
          orderBy = {
            name: orderDirection,
          }
          break
        case "created_at":
        default:
          orderBy = { created_at: orderDirection }
          break
      }
    }

    const whereConditions: any = {}
    const searchConditions: any = {}

    // Add filters
    if (decoded.company_id) {
      whereConditions.company_id = decoded.company_id
    }

    if (status !== undefined && status !== null) {
      whereConditions.status = status
    }

    if (is_paid !== undefined) {
      whereConditions.is_paid = is_paid
    }

    if (level !== undefined) {
      whereConditions.level = level
    }

    if (parent_id !== undefined) {
      whereConditions.parent_id = parent_id
    }

    // Add search functionality
    if (search) {
      searchConditions.name = search
    }

    const leaveTypes: any = await this.leaveTypeRepository.getByParams({
      where: whereConditions,
      search: !isEmpty(searchConditions) ? searchConditions : undefined,
      relations: ["company:id,name", "parentLeave:id,name", "subLeave:id,name"],
      orderBy,
      take,
      skip,
    })

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Leave Types" }),
      leaveTypes,
    )
  }

  async findOne(id: number) {
    const leaveType: any = await this.leaveTypeRepository.getByParams({
      where: { id },
      whereNull: ["deleted_at"],
      relations: ["company:id,name"],
      findOne: true,
    })

    if (isEmpty(leaveType)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Leave Type" }),
      )
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Leave Type" }),
      leaveType,
    )
  }

  async update(
    id: number,
    updateLeaveTypeDto: UpdateLeaveTypeDto,
    token: string,
  ) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const leaveType: any = await this.leaveTypeRepository.getByParams({
      where: { id },
      findOne: true,
    })

    if (isEmpty(leaveType)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Leave Type" }),
      )
    }

    // Check if leave type with same code exists for the company (excluding current record)
    if (updateLeaveTypeDto.code) {
      const existingLeaveType = await this.leaveTypeRepository.getByParams({
        where: {
          company_id: decoded.company_id,
          code: updateLeaveTypeDto.code,
        },
        whereNull: ["deleted_at"],
        whereNotIn: { id: [id] },
        findOne: true,
      })

      if (existingLeaveType) {
        return failureResponse(
          code.VALIDATION,
          validationMessage(messageKey.already_exist, {
            ":data": "Leave Type",
            ":field": "code",
          }),
        )
      }
    }

    // Update the leave type
    Object.assign(leaveType, updateLeaveTypeDto)
    leaveType.updated_by = decoded.user_id
    await this.leaveTypeRepository.save(leaveType)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_update, { ":data": "Leave Type" }),
    )
  }

  async remove(id: number, token: string) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const leaveType: any = await this.leaveTypeRepository.getByParams({
      where: { id },
      whereNull: ["deleted_at"],
      findOne: true,
    })

    if (isEmpty(leaveType)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Leave Type" }),
      )
    }

    await this.leaveTypeRepository.remove({ id: leaveType.id })

    await this.leaveTypeRepository.save({
      id: leaveType.id,
      deleted_by: decoded.user_id,
    })

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_removed, { ":data": "Leave Type" }),
    )
  }

  async activeInactive(id: number, token: string) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const leaveType: any = await this.leaveTypeRepository.getByParams({
      where: { id, company_id: decoded.company_id },
      findOne: true,
    })

    if (isEmpty(leaveType)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Leave Type" }),
      )
    }

    leaveType.status = leaveType.status === 1 ? 0 : 1
    leaveType.updated_by = decoded.user_id

    await this.leaveTypeRepository.save(leaveType)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_update, { ":data": "Leave Type" }),
    )
  }

  async checkLeaveTypeExist(code: string, companyId: number) {
    const leaveType = await this.leaveTypeRepository.getByParams({
      where: {
        code: code,
        company_id: companyId,
      },
      findOne: true,
    })

    return !isEmpty(leaveType)
  }

  async getHierarchy(token: string, filters?: any) {
    const decoded = verifyJwtToken(token)
    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const { page = 1, limit = 10, search, status, is_paid } = filters || {}

    const whereConditions: any = {
      company_id: decoded.company_id,
    }

    const searchConditions: any = {}

    // Add filters
    if (status !== undefined && status !== null) {
      whereConditions.status = status
    } else {
      whereConditions.status = 1 // Default to active only
    }

    if (is_paid !== undefined) {
      whereConditions.is_paid = is_paid
    }

    // Add search functionality
    if (search) {
      searchConditions.name = search
    }

    // Get all leave types for the company
    const leaveTypes: any = await this.leaveTypeRepository.getByParams({
      where: whereConditions,
      search: !isEmpty(searchConditions) ? searchConditions : undefined,
      whereNull: ["deleted_at"],
      orderBy: { level: "ASC", name: "ASC" },
    })

    // Build hierarchy
    const hierarchy: any = this.buildHierarchy(leaveTypes)

    // Apply pagination to root level items only
    const skip = (page - 1) * limit
    const take = parseInt(limit)
    const total = hierarchy.length
    const paginatedHierarchy = hierarchy.slice(skip, skip + take)

    const responseData: any = {
      data: paginatedHierarchy,
      count: total,
      page: parseInt(page),
      limit: parseInt(limit),
      totalPages: Math.ceil(total / take),
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "Leave Types Hierarchy",
      }),
      responseData,
    )
  }

  async getSubLeaveTypes(parentId: number, token: string, filters?: any) {
    const decoded = verifyJwtToken(token)
    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const { page = 1, limit = 10, search, status, is_paid } = filters || {}

    const skip = (page - 1) * limit
    const take = parseInt(limit)

    const whereConditions: any = {
      parent_id: parentId,
      company_id: decoded.company_id,
    }

    const searchConditions: any = {}

    // Add filters
    if (status !== undefined && status !== null) {
      whereConditions.status = status
    } else {
      whereConditions.status = 1 // Default to active only
    }

    if (is_paid !== undefined) {
      whereConditions.is_paid = is_paid
    }

    // Add search functionality
    if (search) {
      searchConditions.name = search
    }

    const subLeaveTypes: any = await this.leaveTypeRepository.getByParams({
      where: whereConditions,
      search: !isEmpty(searchConditions) ? searchConditions : undefined,
      whereNull: ["deleted_at"],
      relations: ["company:id,name", "parentLeave:id,name"],
      orderBy: { name: "ASC" },
      take,
      skip,
    })

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Sub Leave Types" }),
      subLeaveTypes,
    )
  }

  async getSelectableLeaveTypes(token: string, filters?: any) {
    const decoded = verifyJwtToken(token)
    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    const { page = 1, limit = 10, search, status, is_paid } = filters || {}

    const whereConditions: any = {
      company_id: decoded.company_id,
    }

    const searchConditions: any = {}

    // Add filters
    if (status !== undefined && status !== null) {
      whereConditions.status = status
    } else {
      whereConditions.status = 1 // Default to active only
    }

    if (is_paid !== undefined) {
      whereConditions.is_paid = is_paid
    }

    // Add search functionality
    if (search) {
      searchConditions.name = search
    }

    // Get all leave types for the company with filters
    const allLeaveTypes: any = await this.leaveTypeRepository.getByParams({
      where: whereConditions,
      search: !isEmpty(searchConditions) ? searchConditions : undefined,
      whereNull: ["deleted_at"],
      orderBy: { level: "ASC", name: "ASC" },
    })

    // Filter to get only leaf nodes (leave types with no sub leave types)
    const parentIds = allLeaveTypes
      .filter((lt: any) => lt.parent_id !== null)
      .map((lt: any) => lt.parent_id)

    const selectableLeaveTypes = allLeaveTypes.filter((lt: any) => {
      // Include if it's not a parent of any other leave type
      return !parentIds.includes(lt.id)
    })

    // Apply pagination to the filtered results
    const skip = (page - 1) * limit
    const take = parseInt(limit)
    const total = selectableLeaveTypes.length
    const paginatedResults = selectableLeaveTypes.slice(skip, skip + take)

    const responseData: any = {
      data: paginatedResults,
      count: total,
      page: parseInt(page),
      limit: parseInt(limit),
      totalPages: Math.ceil(total / take),
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "Selectable Leave Types",
      }),
      responseData,
    )
  }

  async getMainLeaveTypeForSub(
    subLeaveTypeId: number,
  ): Promise<LeaveType | null> {
    const subLeaveType: any = await this.leaveTypeRepository.getByParams({
      where: { id: subLeaveTypeId },
      findOne: true,
    })

    if (!subLeaveType) return null

    if (subLeaveType.parent_id === null) {
      // It's already a main leave type
      return subLeaveType
    }

    // Get the main leave type (parent)
    const mainLeaveType: any = await this.leaveTypeRepository.getByParams({
      where: { id: subLeaveType.parent_id },
      findOne: true,
    })

    return mainLeaveType || null
  }

  private buildHierarchy(leaveTypes: any[]): any[] {
    const leaveTypeMap = new Map()
    const rootLeaveTypes: any[] = []

    // Create a map of all leave types
    leaveTypes.forEach((leaveType: any) => {
      const hierarchyDto: any = {
        ...leaveType,
        subLeave: [],
        has_sub_leave: false,
        is_selectable: true, // Will be updated below
      }
      leaveTypeMap.set(leaveType.id, hierarchyDto)
    })

    // Build the hierarchy
    leaveTypes.forEach((leaveType: any) => {
      const hierarchyDto = leaveTypeMap.get(leaveType.id)

      if (leaveType.parent_id === null) {
        // Root level leave type
        rootLeaveTypes.push(hierarchyDto)
      } else {
        // Child leave type
        const parentLeave = leaveTypeMap.get(leaveType.parent_id)
        if (parentLeave) {
          parentLeave.subLeave.push(hierarchyDto)
          parentLeave.has_sub_leave = true
          parentLeave.is_selectable = false // Parents are not selectable
        }
      }
    })

    return rootLeaveTypes
  }
}
