import { Injectable } from "@nestjs/common"
import { CreateTimeTrackingDto } from "./dto/create-time-tracking.dto"
import { UpdateTimeTrackingDto } from "./dto/update-time-tracking.dto"
import { TimeTrackingRepository } from "./repositories/time-tracking.repository"
import { ClockInRecordRepository } from "./repositories/clock-in-record.repository"
import { EmployeeRepository } from "../employees/repositories/employee.repository"
import { EmployeesService } from "../employees/employees.service"
import { EmployeeSalaryHistoryRepository } from "../employees/repositories/employee-salary-history.repository"
import {
  convertLocalToUtc,
  getCurrentUtc,
  ensureUtc,
  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 { TimeTracking } from "./entities/time-tracking.entity"
import { ClockInRecord } from "./entities/clock-in-record.entity"
import {
  ClockInResponseDto,
  LastClockInfoResponseDto,
} from "./dto/clock-in-response.dto"
import { verifyJwtToken } from "src/utils/jwt"
import { ProjectRepository } from "../projects/repositories/project.repository"
import { CompanySettingsRepository } from "../company/repositories/company-settings.repository"

@Injectable()
export class TimeTrackingService {
  constructor(
    private readonly timeTrackingRepository: TimeTrackingRepository,
    private readonly clockInRecordRepository: ClockInRecordRepository,
    private readonly employeeRepository: EmployeeRepository,
    private readonly employeesService: EmployeesService,
    private readonly projectRepository: ProjectRepository,
    private readonly companySettingsRepository: CompanySettingsRepository,
    private readonly employeeSalaryHistoryRepository: EmployeeSalaryHistoryRepository,
  ) {}

  async create(createTimeTrackingDto: CreateTimeTrackingDto, token: string) {
    const decoded = verifyJwtToken(token)

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

    // Validate employee exists and belongs to company
    const employee = await this.employeeRepository.getByParams({
      where: {
        id: decoded.employee_id,
        company_id: decoded.company_id,
      },
      findOne: true,
    })

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

    let timeTracking: any = new TimeTracking()
    timeTracking = {
      ...createTimeTrackingDto,
      employee_id: decoded.employee_id,
      company_id: decoded.company_id,
      created_by: decoded.user_id,
      start_time: convertLocalToUtc(createTimeTrackingDto.start_time),
      end_time: createTimeTrackingDto.end_time
        ? convertLocalToUtc(createTimeTrackingDto.end_time)
        : null,
      latitude: createTimeTrackingDto.latitude,
      longitude: createTimeTrackingDto.longitude,
      address: createTimeTrackingDto.address,
    }

    // Calculate total_minutes and cost if end_time is provided
    // Cost calculation now uses salary history based on start_time
    if (timeTracking.end_time) {
      const { total_minutes, cost } = await this.calculateTimeAndCost(
        timeTracking.start_time,
        timeTracking.end_time,
        decoded.employee_id,
      )

      timeTracking.total_minutes = total_minutes
      timeTracking.cost = cost
    }

    await this.timeTrackingRepository.save(timeTracking)

    // Note: Project budget updates removed - cost calculation is now purely based on employee salary

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

  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,
      project_id,
      start_date,
      end_date,
      activity_type_id,
      column_name,
      order = "DESC",
    } = query

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

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

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

    if (!isEmpty(column_name)) {
      switch (column_name) {
        case "employee_name":
          orderBy = {
            "employee.first_name": orderDirection,
            "employee.last_name": orderDirection,
          }
          break
        case "start_time":
          orderBy = { start_time: orderDirection }
          break
        case "end_time":
          orderBy = { end_time: orderDirection }
          break
        case "created_at":
        default:
          orderBy = { start_time: orderDirection }
          break
      }
    }

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

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

    if (decoded.employee_id) {
      whereConditions.employee_id = decoded.employee_id
    }

    if (project_id) {
      whereConditions.project_id = project_id
    }

    if (activity_type_id) {
      whereConditions.activity_type_id = activity_type_id
    }

    // Date range filtering
    if (start_date && end_date) {
      whereConditions.start_time = {
        between: [new Date(start_date), new Date(end_date)],
      }
    } else if (start_date) {
      whereConditions.start_time = {
        gte: new Date(start_date),
      }
    } else if (end_date) {
      whereConditions.start_time = {
        lte: new Date(end_date),
      }
    }

    // Add search functionality
    if (search) {
      searchConditions.description = search
      searchConditions["entity_employee.first_name"] = search
      searchConditions["entity_employee.last_name"] = search
    }

    const timeTrackings: any = await this.timeTrackingRepository.getByParams({
      where: whereConditions,
      search: !isEmpty(searchConditions) ? searchConditions : undefined,
      relations: [
        "company:id,name",
        "employee:id,first_name,last_name",
        "project:id,name",
        "activityType:id,name,location_required",
      ],
      orderBy,
      take,
      skip,
    })

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Time Trackings" }),
      timeTrackings,
    )
  }

  async findOne(id: number) {
    const timeTracking: any = await this.timeTrackingRepository.getByParams({
      where: { id },
      whereNull: ["deleted_at"],
      relations: [
        "company:id,name",
        "employee:id,first_name,last_name",
        "project:id,name",
        "activityType:id,name,location_required",
      ],
      findOne: true,
    })

    if (isEmpty(timeTracking)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Time Tracking" }),
      )
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Time Tracking" }),
      timeTracking,
    )
  }

  async update(
    id: number,
    updateTimeTrackingDto: UpdateTimeTrackingDto,
    token: string,
  ) {
    try {
      const decoded = verifyJwtToken(token)

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

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

      if (isEmpty(timeTracking)) {
        return failureResponse(
          code.VALIDATION,
          errorMessage(messageKey.data_not_found, { ":data": "Time Tracking" }),
        )
      }

      // Update the time tracking
      Object.assign(timeTracking, updateTimeTrackingDto)
      timeTracking.updated_by = decoded.user_id

      // Update dates if provided (ensure UTC conversion)
      if (updateTimeTrackingDto.start_time) {
        timeTracking.start_time = convertLocalToUtc(
          updateTimeTrackingDto.start_time,
        )
      }

      if (updateTimeTrackingDto.end_time) {
        timeTracking.end_time = convertLocalToUtc(
          updateTimeTrackingDto.end_time,
        )
      }

      if (updateTimeTrackingDto.latitude) {
        timeTracking.latitude = updateTimeTrackingDto.latitude
      }

      if (updateTimeTrackingDto.longitude) {
        timeTracking.longitude = updateTimeTrackingDto.longitude
      }

      if (updateTimeTrackingDto.address) {
        timeTracking.address = updateTimeTrackingDto.address
      }

      // Recalculate total_minutes and cost if both start_time and end_time are available
      // Cost calculation now uses salary history based on start_time
      if (timeTracking.start_time && timeTracking.end_time) {
        const { total_minutes, cost } = await this.calculateTimeAndCost(
          timeTracking.start_time,
          timeTracking.end_time,
          decoded.employee_id,
        )

        timeTracking.total_minutes = total_minutes

        // 🔒 Prevent Infinity / NaN from going to DB
        if (!isFinite(cost) || isNaN(cost)) {
          timeTracking.cost = 0
        } else {
          timeTracking.cost = cost
        }
      } else if (updateTimeTrackingDto.end_time === null) {
        // If end_time is being set to null (ongoing activity)
        timeTracking.total_minutes = null
        timeTracking.cost = null
      }

      await this.timeTrackingRepository.save(timeTracking)

      // Note: Project budget updates removed - cost calculation is now purely based on employee salary

      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_update, { ":data": "Time Tracking" }),
      )
    } catch (error) {
      console.log(error)
    }
  }

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

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

    const timeTracking: any = await this.timeTrackingRepository.getByParams({
      where: { id, company_id: decoded.company_id },
      whereNull: ["deleted_at"],
      findOne: true,
    })

    if (isEmpty(timeTracking)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, { ":data": "Time Tracking" }),
      )
    }

    await this.timeTrackingRepository.remove({ id: timeTracking.id })

    await this.timeTrackingRepository.save({
      id: timeTracking.id,
      deleted_by: decoded.user_id,
    })

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

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

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

      const timeTracking: any = await this.timeTrackingRepository.getByParams({
        where: { id, company_id: decoded.company_id },
        whereNull: ["deleted_at"],
        findOne: true,
      })

      if (isEmpty(timeTracking)) {
        return failureResponse(
          code.VALIDATION,
          errorMessage(messageKey.data_not_found, { ":data": "Time Tracking" }),
        )
      }

      if (timeTracking.end_time) {
        return failureResponse(
          code.VALIDATION,
          validationMessage("Time tracking is already stopped"),
        )
      }

      // Get current UTC time using helper function
      const endTime = getCurrentUtc()
      // Cost calculation now uses salary history based on start_time
      const { total_minutes, cost } = await this.calculateTimeAndCost(
        timeTracking.start_time,
        endTime,
        timeTracking.employee_id,
      )

      timeTracking.end_time = endTime
      timeTracking.total_minutes = total_minutes
      timeTracking.cost = cost
      timeTracking.updated_by = decoded.user_id

      await this.timeTrackingRepository.save(timeTracking)

      // Note: Project budget updates removed - cost calculation is now purely based on employee salary

      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_update, { ":data": "Time Tracking" }),
      )
    } catch (error) {
      console.log(error)
      return failureResponse(code.ERROR, errorMessage(messageKey.exception))
    }
  }

  /**
   * Get applicable salary for an employee based on entry date
   * Finds the salary record where from_date <= entryDate and is the most recent matching effective date
   */
  private async getApplicableSalary(
    employeeId: number,
    entryDate: Date,
  ): Promise<{ gross_salary: number; basic_salary: number }> {
    // Get all salary history for the employee ordered by from_date DESC
    const salaryHistory: any =
      await this.employeeSalaryHistoryRepository.getByParams({
        where: {
          employee_id: employeeId,
        },
        whereNull: ["deleted_at"],
        orderBy: { from_date: "DESC" },
      })

    const salaryHistoryArray = Array.isArray(salaryHistory) ? salaryHistory : []

    // Find the most recent salary record where from_date <= entryDate
    const applicableSalaryRecord = salaryHistoryArray.find((record) => {
      const fromDate = new Date(record.from_date)
      return fromDate <= entryDate
    })

    if (applicableSalaryRecord) {
      return {
        gross_salary: parseFloat(
          applicableSalaryRecord.gross_salary.toString(),
        ),
        basic_salary: parseFloat(
          applicableSalaryRecord.basic_salary.toString(),
        ),
      }
    }

    // Fallback to employee's current gross salary if no history exists before that date
    const employee: any = await this.employeeRepository.getByParams({
      where: { id: employeeId },
      whereNull: ["deleted_at"],
      findOne: true,
    })

    if (!employee || !employee.gross_salary) {
      return { gross_salary: 0, basic_salary: 0 }
    }

    return {
      gross_salary: parseFloat(employee.gross_salary.toString()),
      basic_salary: parseFloat(employee.basic_salary?.toString() || "0"),
    }
  }

  private async calculateTimeAndCost(
    startTime: Date,
    endTime: Date,
    employeeId: number,
  ): Promise<{ total_minutes: number; cost: number }> {
    const startUTC = ensureUtc(startTime)
    const endUTC = ensureUtc(endTime)

    const timeDiff = endUTC.getTime() - startUTC.getTime()
    const total_minutes = Math.floor(timeDiff / (1000 * 60))

    if (total_minutes <= 0) {
      return { total_minutes: 0, cost: 0 }
    }

    const { gross_salary } = await this.getApplicableSalary(
      employeeId,
      startUTC,
    )

    if (!gross_salary || gross_salary === 0) {
      return { total_minutes, cost: 0 }
    }

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

    if (!employee) {
      return { total_minutes, cost: 0 }
    }

    const companySettings: any =
      await this.companySettingsRepository.getByParams({
        where: { company_id: employee.company_id },
        whereNull: ["deleted_at"],
        findOne: true,
      })

    const workingHoursPerDay = companySettings?.working_hours_per_day ?? 8

    const workingDaysPerMonth = companySettings?.working_days_per_month ?? 22

    const totalWorkingMinutesPerMonth =
      workingHoursPerDay > 0 && workingDaysPerMonth > 0
        ? workingHoursPerDay * workingDaysPerMonth * 60
        : 0

    if (totalWorkingMinutesPerMonth <= 0) {
      return { total_minutes, cost: 0 }
    }

    const perMinuteRate = gross_salary / totalWorkingMinutesPerMonth

    const calculatedCost = perMinuteRate * total_minutes

    const cost = Number.isFinite(calculatedCost)
      ? Math.round(calculatedCost * 100) / 100
      : 0

    return { total_minutes, cost }
  }

  async clockInOut(token: string): Promise<any> {
    const decoded = verifyJwtToken(token)

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

    try {
      const { employee_id, company_id } = decoded

      // Get the latest clock-in record for this employee
      const latestRecord = await this.getLatestClockInByEmployee(
        employee_id,
        company_id,
      )

      const now = convertLocalToUtc(new Date())

      if (!latestRecord || latestRecord.clock_out) {
        // Clock In: Create new record
        const newRecord = await this.clockInRecordRepository.save({
          company_id,
          employee_id,
          clock_in: now,
          clock_out: null,
          duration: null,
          created_by: employee_id,
        })

        const response: ClockInResponseDto = {
          status: "CLOCKED_IN",
          message: "Successfully clocked in",
          clock_in_time: newRecord.clock_in,
        }

        return successResponse(
          code.SUCCESS,
          successMessage(messageKey.data_add, { ":data": "Clock In" }),
          response as any,
        )
      } else {
        // Clock Out: Update existing record
        const clockInTime = new Date(latestRecord.clock_in)
        const clockOutTime = now
        const durationMinutes = Math.floor(
          (clockOutTime.getTime() - clockInTime.getTime()) / (1000 * 60),
        )

        await this.clockInRecordRepository.save(
          {
            clock_out: clockOutTime,
            duration: durationMinutes,
            updated_by: employee_id,
          },
          { id: latestRecord.id },
        )

        const response: ClockInResponseDto = {
          status: "CLOCKED_OUT",
          message: "Successfully clocked out",
          clock_in_time: latestRecord.clock_in,
          clock_out_time: clockOutTime,
          duration: this.formatDuration(durationMinutes),
        }

        return successResponse(
          code.SUCCESS,
          successMessage(messageKey.data_update, { ":data": "Clock Out" }),
          response as any,
        )
      }
    } catch (error) {
      return failureResponse(code.ERROR, errorMessage(messageKey.exception))
    }
  }

  async getLastClockInfo(token: string): Promise<any> {
    const decoded = verifyJwtToken(token)

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

    try {
      const { employee_id, company_id } = decoded

      // Get latest clock-in record
      const lastClockIn = await this.getLatestClockInByEmployee(
        employee_id,
        company_id,
      )

      // Get latest time tracking record
      const lastTimeTracking: any =
        await this.timeTrackingRepository.getByParams({
          where: { employee_id, company_id },
          whereNull: ["deleted_at"],
          orderBy: { created_at: "DESC" },
          findOne: true,
          relations: ["project", "activityType"],
        })

      const response: LastClockInfoResponseDto = {
        lastClockIn: lastClockIn
          ? {
              id: lastClockIn.id,
              clock_in: lastClockIn.clock_in,
              clock_out: lastClockIn.clock_out,
              duration: lastClockIn.clock_out
                ? this.formatDuration(lastClockIn.duration || 0)
                : this.formatDuration(
                    Math.floor(
                      (new Date().getTime() -
                        new Date(lastClockIn.clock_in).getTime()) /
                        (1000 * 60),
                    ),
                  ),
              status: lastClockIn.clock_out ? "COMPLETED" : "ONGOING",
            }
          : null,
        lastTimeTracking: lastTimeTracking
          ? {
              id: lastTimeTracking.id,
              project_name: lastTimeTracking.project?.name || "N/A",
              activity_type: lastTimeTracking.activityType?.name || "N/A",
              start_time: lastTimeTracking.start_time,
              end_time: lastTimeTracking.end_time,
              total_minutes: lastTimeTracking.total_minutes,
              description: lastTimeTracking.description,
            }
          : null,
      }

      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_retrieve, {
          ":data": "Last Clock Info",
        }),
        response as any,
      )
    } catch (error) {
      return failureResponse(code.ERROR, errorMessage(messageKey.exception))
    }
  }

  private formatDuration(minutes: number): string {
    const hours = Math.floor(minutes / 60)
    const mins = minutes % 60
    return `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}`
  }

  private async getLatestClockInByEmployee(
    employee_id: number,
    company_id: number,
  ): Promise<ClockInRecord | null> {
    return this.clockInRecordRepository.getByParams({
      where: {
        employee_id,
        company_id,
      },
      whereNull: ["deleted_at"],
      orderBy: { created_at: "DESC" },
      findOne: true,
      relations: ["employee"],
    }) as Promise<ClockInRecord | null>
  }
}
