import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ErrorService } from '../../error/error.service';
import {
  applyFiltersToBuilder,
  applySortingToBuilder,
  paginationResult,
} from '../../utils';
import { CreateAddressDto } from './dto/create-address.dto';
import { UpdateAddressDto } from './dto/update-address.dto';
import { AddressEntity } from './entities/address.entity';
import { CityEntity, CityIds } from './entities/city.entity';
import { StateEntity } from './entities/state.entity';

@Injectable()
export class AddressService {
  constructor(
    @InjectRepository(AddressEntity)
    private addressRepository: Repository<AddressEntity>,
    @InjectRepository(CityEntity)
    private cityRepository: Repository<CityEntity>,
    @InjectRepository(StateEntity)
    private stateRepository: Repository<StateEntity>,
    private error: ErrorService,
  ) {}

  /**
   * -------------------------------------------------------
   */
  async getAll(
    page = 1,
    limit = 20,
    filters = null,
    sorts = null,
    userId = null,
  ) {
    let builder = this._findBuilder();

    builder.take(limit);
    builder.skip((page - 1) * limit);

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    } else {
      builder.orderBy('address.createdAt', 'DESC');
    }

    builder = applyFiltersToBuilder(builder, filters);

    // Custom condition
    if (userId) {
      builder.andWhere({ userId });
    }

    const [items, totalItems] = await builder.getManyAndCount();
    return {
      items: items.map(this._findTransform),
      pagination: paginationResult(page, limit, totalItems),
    };
  }

  /**
   * -------------------------------------------------------
   */
  async getById(id: number, userId = null) {
    const builder = this._findBuilder();
    builder.andWhere({ id });

    if (userId) {
      builder.andWhere({ userId });
    }

    const item = await builder.getOne();
    return this._findTransform(item);
  }

  /**
   * -------------------------------------------------------
   */
  async addByAdmin(dto: CreateAddressDto) {
    const newAddress = new AddressEntity();
    for (const key in dto) {
      newAddress[key] = dto[key];
    }
    // hard code
    newAddress.location = `${dto.lat} , ${dto.lng}`;
    newAddress.createdAt = new Date();
    newAddress.updatedAt = new Date();

    const { identifiers } = await this.addressRepository
      .createQueryBuilder()
      .insert()
      .values(newAddress)
      .execute();

    const newAddressId = identifiers[0].id;
    return await this.getById(newAddressId);
  }

  /**
   * -------------------------------------------------------
   */
  async updateByAdmin(id: number, dto: UpdateAddressDto) {
    return await this.addressRepository
      .createQueryBuilder()
      .update()
      .set({
        name: dto.name,
        address: dto.address,
        cityId: dto.cityId,
        location: `${dto.lat} , ${dto.lng}`,
        updatedAt: new Date(),
      })
      .where({ id })
      .execute();
  }

  /**
   * -------------------------------------------------------
   */
  async deleteAddress(id: number) {
    return await this.addressRepository
      .createQueryBuilder()
      .update()
      .set({ deleted: true, updatedAt: new Date() })
      .where({ id })
      .execute();
  }

  /**
   * -------------------------------------------------------
   * province
   */
  async getAllStates() {
    return await this.stateRepository.find({
      select: ['id', 'name'],
    });
  }

  /**
   * -------------------------------------------------------
   */
  async getAllCities() {
    return await this.cityRepository.find({
      select: ['id', 'name', 'stateId', 'mahexCode'],
    });
  }

  /**
   * -------------------------------------------------------
   */
  private _findBuilder() {
    const builder = this.addressRepository.createQueryBuilder('address');

    builder.leftJoin('address.city', 'city');
    builder.leftJoin('city.state', 'state');
    builder.leftJoin('address.user', 'user');

    builder.select([
      'address.id',
      'address.name',
      'address.location',
      'address.address',
      'address.createdAt',

      'city.id',
      'city.name',

      'state.id',
      'state.name',

      'user.id',
      'user.name',
      'user.surName',
    ]);
    return builder;
  }

  /**
   * -------------------------------------------------------
   */
  async getRecordById(id: number, userId: string = null) {
    return await this.addressRepository.findOne({
      where: { id, ...(userId && { userId }) },
      relations: ['city'],
    });
  }

  /**
   * -------------------------------------------------------
   */
  async calculateShipping(addressId: number) {
    const address = await this.getRecordById(addressId);
    if (!address) {
      this.error.unprocessableEntity(['آدرس ورودی معتبر نمی‌باشد']);
    }

    // TODO: alopeyk

    // TODO: hard data for Tehran
    const data = {
      price: 0,
      address: null,
      city: null,
      cityFa: null,
      distance: null,
      duration: null,
      transportType: null,
      title: null, // "الوپیک",
    };

    if (address.cityId === CityIds.tehran) {
      data.price = 40000;
    }

    return data;
  }

  /**
   * -------------------------------------------------------
   */
  private _findTransform(item: AddressEntity) {
    const [lat, lng] = item.location.split(' , ');
    delete item.location;
    return { ...item, lat, lng };
  }
}
