import { Injectable } from "@nestjs/common"
import { CreateConsultantDto } from "./dto/create-consultant.dto"
import { UpdateConsultantDto } from "./dto/update-consultant.dto"
import { ConsultantFiltersDto } from "./dto/consultant-filters.dto"
import { ConsultantRepository } from "./repositories/consultant.repository"
import { PartyTypesService } from "../party-types/party-types.service"
import { PartyTypeCategory } from "../party-types/entities/party-type.entity"
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 { Consultant } from "./entities/consultant.entity"
import { verifyJwtToken } from "src/utils/jwt"

@Injectable()
export class ConsultantsService {
  constructor(
    private readonly consultantRepository: ConsultantRepository,
    private readonly partyTypesService: PartyTypesService,
  ) {}

  // ==================== CONSULTANT TYPES ====================

  async getTypes(token: string) {
    return this.partyTypesService.getTypesByCategory(
      PartyTypeCategory.CONSULTANT,
      token,
    )
  }

  // ==================== CONSULTANTS ====================

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

    // Check if consultant with same name exists for the company
    const existingConsultant = await this.consultantRepository.getByParams({
      where: {
        company_id: decoded.company_id,
        consultant_name: createConsultantDto.consultant_name,
      },
      findOne: true,
    })

    if (existingConsultant) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.already_exist, {
          ":data": "Consultant",
          ":field": "consultant_name",
        }),
      )
    }

    let consultant: any = new Consultant()
    consultant = {
      ...createConsultantDto,
      company_id: decoded.company_id,
      created_by: decoded.user_id,
    }

    await this.consultantRepository.save(consultant)

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

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

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

    const {
      page = 1,
      limit = 10,
      search,
      type_id,
      project_id,
      column_name,
      order = "DESC",
    } = query

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

    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":
        case "consultant_name":
          orderBy = { consultant_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 (type_id) {
      whereConditions.type_id = type_id
    }

    if (project_id) {
      whereConditions.project_id = project_id
    }

    // Add search functionality for name, phone, and email
    if (search) {
      searchConditions.consultant_name = search
      searchConditions.phone_number = search
      searchConditions.email = search
    }

    const consultants: any = await this.consultantRepository.getByParams({
      where: whereConditions,
      search: !isEmpty(searchConditions) ? searchConditions : undefined,
      relations: [
        "company:id,name",
        "partyType:id,type_name",
        "project:id,name",
      ],
      orderBy,
      take,
      skip,
    })

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Consultants" }),
      consultants,
    )
  }

  async findOne(id: number) {
    const consultant: any = await this.consultantRepository.getByParams({
      where: { id },
      whereNull: ["deleted_at"],
      relations: [
        "company:id,name",
        "partyType:id,type_name",
        "project:id,name",
      ],
      findOne: true,
    })

    if (isEmpty(consultant)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Consultant" }),
      )
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Consultant" }),
      consultant,
    )
  }

  async update(
    id: number,
    updateConsultantDto: UpdateConsultantDto,
    token: string,
  ) {
    const decoded = verifyJwtToken(token)

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

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

    if (isEmpty(consultant)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Consultant" }),
      )
    }

    // Check if consultant with same name exists for the company (excluding current record)
    if (updateConsultantDto.consultant_name) {
      const existingConsultant = await this.consultantRepository.getByParams({
        where: {
          company_id: decoded.company_id,
          consultant_name: updateConsultantDto.consultant_name,
        },
        whereNull: ["deleted_at"],
        whereNotIn: { id: [id] },
        findOne: true,
      })

      if (existingConsultant) {
        return failureResponse(
          code.VALIDATION,
          validationMessage(messageKey.already_exist, {
            ":data": "Consultant",
            ":field": "consultant_name",
          }),
        )
      }
    }

    // Update the consultant
    Object.assign(consultant, updateConsultantDto)
    consultant.updated_by = decoded.user_id
    await this.consultantRepository.save(consultant)

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

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

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

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

    if (isEmpty(consultant)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Consultant" }),
      )
    }

    await this.consultantRepository.remove({ id: consultant.id })

    await this.consultantRepository.save({
      id: consultant.id,
      deleted_by: decoded.user_id,
    })

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

  async checkConsultantExist(name: string, companyId: number) {
    const consultant = await this.consultantRepository.getByParams({
      where: {
        consultant_name: name,
        company_id: companyId,
      },
      findOne: true,
    })

    return !isEmpty(consultant)
  }

  async checkConsultantTypeExist(typeName: string, companyId: number) {
    return this.partyTypesService.checkPartyTypeExist(
      typeName,
      PartyTypeCategory.CONSULTANT,
      companyId,
    )
  }
}
