import { Injectable } from "@nestjs/common"
import { CityRepository } from "../repositories/city.repository"
import { CreateCityDto } from "../dto/create-city.dto"
import { code } from "src/common/response/response.code"
import { isEmpty, successMessage } from "src/utils/helpers"
import { failureResponse, successResponse } from "src/common/response/response"
import { FindAllCityDto } from "../dto/find-all-city.dto"
import { City } from "../entities/city.entity"
import { UpdateCityDto } from "../dto/update-city.dto"
import { messageKey } from "src/constants/message-keys"
import { Repository } from "typeorm"
import { InjectRepository } from "@nestjs/typeorm"
import { UpsertLocationDto } from "../dto/upsert-location.dto"
import { CountryRepository } from "../../country/repositories/country.repository"
import { StateRepository } from "../../state/repositories/state.repository"

@Injectable()
export class CityService {
  constructor(
    private readonly cityRepository: CityRepository,
    private readonly countryRepository: CountryRepository,
    private readonly stateRepository: StateRepository,
    @InjectRepository(City)
    private readonly cityModelRepository: Repository<City>,
  ) {}

  async checkCityExist(name: string, excludeId?: number): Promise<boolean> {
    const whereCondition: any = {}
    const wherelowerCondition: any = {
      name,
    }

    if (excludeId) {
      whereCondition["id"] = {
        not: excludeId,
      }
    }

    const currency = await this.cityRepository.getByParams({
      where: whereCondition,
      whereLower: wherelowerCondition,
      findOne: true,
    })

    return !isEmpty(currency)
  }

  async createCity(createCityDto: CreateCityDto) {
    const isCityExist = await this.checkCityExist(createCityDto.name)

    if (isCityExist) {
      return failureResponse(400, messageKey.already_exist)
    }

    try {
      const result = await this.cityRepository.save({
        name: createCityDto.name,
        state_id: createCityDto.state_id,
        country_id: createCityDto.country_id,
      })

      return result
    } catch (error) {
      return failureResponse(500, messageKey.something_went_wrong)
    }
  }

  async findAllCities(params: FindAllCityDto) {
    const validSortColumns = ["id", "name", "created_at", "updated_at"]

    let sortColumn: string
    let sortOrder: "ASC" | "DESC"

    if (params.sortBy && validSortColumns.includes(params.sortBy)) {
      sortColumn = params.sortBy
      sortOrder = params.sortOrder || "ASC"
    } else {
      sortColumn = "created_at"
      sortOrder = "DESC"
    }

    const filters: any = {}

    if (params?.country_id) {
      filters["country_id"] = params.country_id.split(",")
    }

    if (params?.state_id) {
      filters["state_id"] = params.state_id.split(",")
    }

    const cities = await this.cityRepository.getByParams({
      search: {
        name: params.search,
      },
      whereIn: filters,
      take: Number(params.limit),
      skip: Number(params.skip),
      orderBy: {
        [sortColumn]: sortOrder,
      },
      relations: ["state:id,name,country_id", "country:id,name"],
      select: ["id", "name", "state_id", "country_id", "created_at"],
    })

    return {
      success: true,
      code: 200,
      message: messageKey.data_retrieve,
      data: cities,
    }
  }

  async findOneCity(id: number): Promise<City> {
    const city = (await this.cityRepository.getByParams({
      where: { id },
      findOne: true,
      relations: ["state:id,name", "country:id,name"],
      select: ["id", "name", "state_id", "country_id"],
    })) as City

    if (!city) {
      throw new Error(messageKey.data_not_found)
    }

    return city
  }

  async updateCity(id: number, updateCityDto: UpdateCityDto) {
    const city = (await this.cityRepository.getByParams({
      where: { id },
      findOne: true,
    })) as City

    if (!city) {
      return failureResponse(404, messageKey.data_not_found)
    }

    const isCityExist = await this.checkCityExist(updateCityDto.name, id)

    if (isCityExist) {
      return failureResponse(400, messageKey.already_exist)
    }

    Object.assign(city, updateCityDto)

    return await this.cityRepository.save(city)
  }

  async remove(id: number) {
    try {
      const city = (await this.cityRepository.getByParams({
        where: { id },
        findOne: true,
        relations: ["addresses"],
      })) as City

      if (!city) {
        return failureResponse(code.DATA_NOT_FOUND, messageKey.data_not_found)
      }

      if (city.addresses?.length > 0) {
        return failureResponse(
          code.BAD_REQUEST,
          "Cannot delete this city because it has associated addresses",
        )
      }

      await this.cityModelRepository.softDelete(id)

      return successMessage(messageKey.data_removed, { ":data": "city" })
    } catch (error) {
      return failureResponse(code.ERROR, messageKey.something_went_wrong)
    }
  }

  async getCityDropdown() {
    try {
      const result = await this.cityRepository.getByParams({
        select: ["id", "name"],
        orderBy: { name: "ASC" },
        findOne: false,
      })

      return result
    } catch (error) {
      return failureResponse(500, messageKey.something_went_wrong)
    }
  }

  async getCityDetailsForDropdown(id: number) {
    const city = await this.cityRepository.getByParams({
      where: { id },
      findOne: true,
      relations: ["state:id,name", "country:id,name"],
      select: ["id", "name", "state_id", "country_id"],
    })

    if (!city) {
      return failureResponse(404, messageKey.data_not_found)
    }

    return {
      success: true,
      code: 200,
      message: messageKey.data_retrieve,
      data: city,
    }
  }

  async upsertLocation(dto: UpsertLocationDto) {
    let country
    let state
    let city

    country = await this.countryRepository.getByParams({
      whereLower: { name: dto?.country },
      findOne: true,
    })

    if (isEmpty(country)) {
      country = await this.countryRepository.save({
        name: dto.country,
      })
    }

    state = await this.stateRepository.getByParams({
      where: {
        country_id: country?.id,
      },
      whereLower: {
        name: dto.state,
      },
      findOne: true,
    })

    if (isEmpty(state)) {
      state = await this.stateRepository.save({
        name: dto.state,
        country_id: country?.id,
      })
    }

    city = await this.cityRepository.getByParams({
      where: {
        state_id: state.id,
        country_id: country.id,
      },
      whereLower: { name: dto.city },
      findOne: true,
    })

    if (isEmpty(city)) {
      city = await this.cityRepository.save({
        name: dto.city,
        state_id: state.id,
        country_id: country.id,
      })
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Trips" }),
      {
        city,
        state,
        country,
      },
    )
  }
}
