import { Injectable } from "@nestjs/common"
import { CreateIncidentReportingDto } from "../dto/create-incident-reporting.dto"
import { IncidentTypeRepository } from "../repositories/incident-types.repository"
import { errorMessage, isEmpty, successMessage } from "src/utils/helpers"
import { IncidentType } from "../entities/incident-type.entity"
import { IncidentReportingRepository } from "../repositories/incident-report.repository"
import { FleetManagementRepository } from "../../fleet-management/repositories/fleet-management.repository"
import { failureResponse, successResponse } from "src/common/response/response"
import { code } from "src/common/response/response.code"
import { messageKey } from "src/constants/message-keys"
import { TeamMemberRepository } from "../../team-member/repositories/team_member.repository"
import { RoleRepository } from "../../role/repositories/role.repository"
import { IncidentImageRepository } from "../repositories/incident-image.repository"
import { IncidentReporting } from "../entities/incident-reporting.entity"
import { TripRepository } from "../../trips/repositories/trip.repository"
import { ConfigService } from "@nestjs/config"
import { fileStoreLocation } from "src/common/file-upload/file-store-location"
import { GetIncidentQueryDto } from "../dto/incident-reporting-filter.dto"
import { UserLoginRepository } from "../../auth/repositories/user-login.repository"
import { InjectRepository } from "@nestjs/typeorm"
import { Repository } from "typeorm"
import { AuthService } from "../../auth/v1/auth.service"
import { NotificationService } from "../../notification/v1/notification.service"
import { formatMessage } from "src/constants/notification-message"
import { TeamMember } from "../../team-member/entities/team_member.entity"

@Injectable()
export class IncidentReportingService {
  constructor(
    private readonly incidentTypeRepository: IncidentTypeRepository,
    private readonly incidentReportingRepository: IncidentReportingRepository,
    private readonly fleetRepository: FleetManagementRepository,
    private readonly teamMemberRepository: TeamMemberRepository,
    private readonly roleRepository: RoleRepository,
    private readonly incidentImageRepository: IncidentImageRepository,
    private readonly tripRepository: TripRepository,
    private readonly userLoginRepository: UserLoginRepository,
    private readonly authService: AuthService,
    private readonly notificationService: NotificationService,

    @InjectRepository(IncidentReporting)
    private readonly incidentReportingEntityRepository: Repository<IncidentReporting>,
    private readonly configService: ConfigService,
  ) {}

  async create(
    createIncidentReportingDto: CreateIncidentReportingDto,
    files: { images?: Express.Multer.File[] },
  ) {
    const isFleetExist = await this.fleetRepository.getByParams({
      where: {
        id: createIncidentReportingDto?.fleet_id,
      },
      findOne: true,
    })

    if (isEmpty(isFleetExist)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, { ":data": "Fleet" }),
      )
    }

    if (
      !isEmpty(createIncidentReportingDto?.trip_id) &&
      createIncidentReportingDto?.trip_id !== 0
    ) {
      const tripExist = await this.tripRepository.getByParams({
        where: {
          id: createIncidentReportingDto?.trip_id,
        },
        findOne: true,
      })

      if (isEmpty(tripExist)) {
        return failureResponse(
          code.DATA_NOT_FOUND,
          errorMessage(messageKey.data_not_found, { ":data": "Trip" }),
        )
      }
    }

    const isIncidentTypeExist = await this.incidentTypeRepository.getByParams({
      where: {
        id: createIncidentReportingDto?.issue_type_id,
      },
      findOne: true,
    })

    if (isEmpty(isIncidentTypeExist)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, { ":data": "Incident type" }),
      )
    }

    const driverRoleId: any = await this.roleRepository.getByParams({
      whereLower: {
        name: "driver",
      },
      findOne: true,
      select: ["id", "name"],
    })

    const isDriverExist = (await this.teamMemberRepository.getByParams({
      where: {
        id: createIncidentReportingDto?.driver_id,
        role_id: driverRoleId?.id,
      },
      select: ["id", "first_name", "last_name", "reporting_to_id"],
      findOne: true,
    })) as TeamMember

    const reportingTo = (await this.teamMemberRepository.getByParams({
      where: {
        id: isDriverExist?.reporting_to_id,
      },
      select: ["id", "first_name", "last_name"],
      relations: ["users:id,first_name,last_name"],
      findOne: true,
    })) as TeamMember

    if (isEmpty(isDriverExist)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, { ":data": "Driver" }),
      )
    }

    const incidentReport = new IncidentReporting()

    if (createIncidentReportingDto?.trip_id === 0) {
      delete createIncidentReportingDto.trip_id
    }

    Object.assign(incidentReport, createIncidentReportingDto)

    const savedIncident =
      await this.incidentReportingRepository.save(incidentReport)

    if (files?.images?.length > 0) {
      for (const image of files.images) {
        const incidentImage: any = {}
        Object.assign(incidentImage, {
          incident_id: savedIncident?.id,
          image_url: `${fileStoreLocation.incident_reporting}/${image.filename}`,
        })
        await this.incidentImageRepository.save(incidentImage)
      }
    }

    const admins = await this.authService.getAdmins()
    const adminIds = admins.map((admin) => admin.id)
    if (reportingTo) {
      adminIds.push(reportingTo.users[0].id)
    }
    if (adminIds.length > 0) {
      this.notificationService.sendNotification({
        user_ids: adminIds,
        title: "Incident Report",
        message: formatMessage("driverIncidentReport", {
          DRIVER: isDriverExist?.first_name + " " + isDriverExist?.last_name,
          TRIP_ID: createIncidentReportingDto?.trip_id
            ? ` #T${createIncidentReportingDto?.trip_id?.toString()}`
            : "",
          REPORT_ID: savedIncident?.id?.toString(),
        }),
        data: {
          type: "incident_report",
          incident_id: savedIncident?.id,
          driver_id: isDriverExist?.id,
        },
      })
    }

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

  async findAll(token: any, query: GetIncidentQueryDto) {
    const defaultPagination = {
      take: this.configService.get<number>("APP.pagination.take") || 10,
      skip: this.configService.get<number>("APP.pagination.skip") || 0,
    }

    const { limit, skip, type, driver_id, search } = query

    const userDetail: any = await this.userLoginRepository.getByParams({
      where: { access_token: token },
      findOne: true,
      select: ["id", "user_id"],
      relations: ["user:id,team_member_id,role_id.role:id,name"],
    })

    const qb = this.incidentReportingEntityRepository
      .createQueryBuilder("incident")
      .leftJoinAndSelect("incident.fleet_management", "fleet_management")
      .leftJoinAndSelect("incident.incident_type", "incident_type")
      .leftJoinAndSelect("incident.images", "images")
      .leftJoinAndSelect("incident.team_member", "team_member")
      .leftJoinAndSelect("team_member.reporting_to", "reporting_to")
      .orderBy("incident.created_date", "DESC")
      .addOrderBy("incident.created_time", "DESC")
      .skip(Number(skip) || defaultPagination.skip)
      .take(Number(limit) || defaultPagination.take)
      .select([
        "incident.id",
        "incident.driver_id",
        "incident.fleet_id",
        "incident.trip_id",
        "incident.created_date",
        "incident.created_time",
        "incident.issue_title",
        "incident.comment",
        "fleet_management.id",
        "fleet_management.registration_number",
        "fleet_management.vin_number",
        "incident_type.id",
        "incident_type.name",
        "images.id",
        "images.image_url",
        "team_member.id",
        "team_member.first_name",
        "team_member.last_name",
        "team_member.profile_photo",
        "reporting_to.id",
        "reporting_to.first_name",
        "reporting_to.last_name",
        "reporting_to.profile_photo",
      ])

    // filters
    if (type) {
      qb.andWhere("incident.issue_type_id = :type", { type })
    }

    if (driver_id) {
      qb.andWhere("incident.driver_id = :driverId", { driverId: driver_id })
    }

    if (search) {
      qb.andWhere("incident.issue_title ILIKE :search", {
        search: `%${search}%`,
      })
    }

    // dispatcher-specific restriction
    if (userDetail?.user?.role?.name.toLowerCase() === "dispatcher") {
      qb.andWhere("reporting_to.id = :dispatcherId", {
        dispatcherId: userDetail.user?.team_member_id,
      })
    } else if (userDetail?.user?.role?.name.toLowerCase() === "driver") {
      qb.andWhere("incident.driver_id = :driverId", {
        driverId: userDetail.user?.team_member_id,
      })
    }

    const [incidents, count] = await qb.getManyAndCount()

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Incident" }),
      { count, data: incidents },
    )
  }

  async findOne(id: number) {
    const incidentReporting =
      await this.incidentReportingRepository.getByParams({
        where: {
          id: id,
        },
        relations: [
          "fleet_management:id,registration_number,vin_number",
          "incident_type:id,name",
          "images:id,incident_id,image_url",
          "team_member:id,first_name,last_name,profile_photo",
        ],
        select: [
          "id",
          "incident_type",
          "driver_id",
          "fleet_id",
          "trip_id",
          "created_date",
          "created_time",
          "issue_title",
        ],
        findOne: true,
      })

    if (isEmpty(incidentReporting)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, {
          ":data": "Incident reporting",
        }),
      )
    }

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

  async findAllIncidentType() {
    const incidentType = await this.incidentTypeRepository.getByParams({})

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Incident type" }),
      incidentType,
    )
  }

  async checkIncidentExist(name: any) {
    const isIncidentTypeExist = await this.incidentTypeRepository.getByParams({
      where: {
        name: name,
      },
      findOne: true,
    })

    return isEmpty(isIncidentTypeExist)
  }

  async createIncidentType(type: IncidentType) {
    const isTypeExist = await this.checkIncidentExist(type?.name)

    if (isTypeExist) {
      const incidentType = new IncidentType()
      Object.assign(incidentType, type)

      await this.incidentTypeRepository.save(incidentType)
    }
  }
}
