"use strict";
const db = require("../../models/index");
const sequelize = require("sequelize");
const Op = sequelize.Op;
const { AD_CATEGORY } = require("../../common/enums");

const bulkUpsertRealEstates = async realEstateData => {
  try {
    const fieldsToUpdateIfDuplicate = [
      "realEstateType",
      "adType",
      "price",
      "area",
      "streetNumber",
      "streetName",
      "locality",
      "municipality",
      "city",
      "region",
      "entity",
      "country",
      "locationLat",
      "locationLong",
      "title",
      "shortDescription",
      "longDescription",
      "gardenSize",
      "adStatus",
      "updatedAt",
      "renewedDate",
      "numberOfRooms",
      "numberOfFloors",
      "floor",
      "accessRoadType",
      "heatingType",
      "furnishingType",
      "balcony",
      "newBuilding",
      "elevator",
      "water",
      "electricity",
      "drainageSystem",
      "registeredInZkBooks",
      "recentlyAdapted",
      "parking",
      "garage",
      "gas",
      "antiTheftDoor",
      "airCondition",
      "phoneConnection",
      "cableTV",
      "internet",
      "basementAttic",
      "storeRoom",
      "videoSurveillance",
      "alarm",
      "suitableForStudents",
      "includingBills",
      "animalsAllowed",
      "pool",
      "urbanPlanPermit",
      "buildingPermit",
      "utilityConnection",
      "distanceToRiver",
      "numberOfViewsAgency"
    ];
    const order = [["updatedAt", "desc"]];

    return await db.RealEstate.bulkCreate(realEstateData, {
      updateOnDuplicate: fieldsToUpdateIfDuplicate,
      returning: true,
      order
    });
  } catch (e) {
    console.log("Error bulk upserting realEstates : ", e);
  }
};

const getRealEstateById = async id => {
  return db.RealEstate.findByPk(id);
};

const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
  const {
    priceMin,
    priceMax,
    sizeMin,
    sizeMax,
    adType,
    realEstateType,
    areaToSearch,
    gardenSizeMin,
    gardenSizeMax,
    numberOfRoomsMin,
    numberOfRoomsMax,
    numberOfFloorsMin,
    numberOfFloorsMax,
    floorMin,
    floorMax,
    includeIncompleteAds,
    includeWithoutPrice,
    balcony,
    elevator,
    newBuilding,
    accessRoadType
  } = searchRequest;

  //Needed for defining which attribute should exist or not
  const realEstateTypeObject = AD_CATEGORY[realEstateType];

  const longitudeColumn = sequelize.col("locationLong");
  const latitudeColumn = sequelize.col("locationLat");

  const pointGeometry = sequelize.fn(
    "ST_Point",
    longitudeColumn,
    latitudeColumn
  );
  const pointWithSRID = sequelize.fn("ST_SetSRID", pointGeometry, 4326);
  const areaToSearchAsGeometry = sequelize.fn(
    "ST_GeomFromGeoJSON",
    JSON.stringify(areaToSearch)
  );
  const areaToSearchWithSRID = sequelize.fn(
    "ST_SetSRID",
    areaToSearchAsGeometry,
    4326
  );
  const contains = sequelize.fn(
    "ST_Contains",
    areaToSearchWithSRID,
    pointWithSRID
  );

  const geoSearchQueryPart = sequelize.where(contains, true);

  //General queries contain only attributes that are defined for every searchreq

  //Query for case of complete ads
  const query = {
    adType,
    realEstateType,
    area: {
      [Op.lte]: sizeMax,
      [Op.gte]: sizeMin
    },
    [Op.and]: geoSearchQueryPart
  };

  //Query for case of incomplete ads
  const queryIncludeIncomplete = {
    adType,
    realEstateType,
    area: {
      [Op.or]: {
        [Op.and]: {
          [Op.lte]: sizeMax,
          [Op.gte]: sizeMin
        },
        [Op.is]: null
      }
    },
    [Op.and]: geoSearchQueryPart
  };

  //Is user unchecked includeWithoutPrice FALSE then it shouldn't return null values of price
  //If not then null values are accepted (this is DEFAULT)
  //includeIncpompleteAds does not have effect on price query
  if (includeWithoutPrice) {
    query.price = {
      [Op.or]: {
        [Op.and]: {
          [Op.lte]: priceMax,
          [Op.gte]: priceMin
        },
        [Op.is]: null
      }
    };
    queryIncludeIncomplete.price = {
      [Op.or]: {
        [Op.and]: {
          [Op.lte]: priceMax,
          [Op.gte]: priceMin
        },
        [Op.is]: null
      }
    };
  } else {
    query.price = {
      [Op.and]: {
        [Op.lte]: priceMax,
        [Op.gte]: priceMin
      }
    };
    queryIncludeIncomplete.price = {
      [Op.and]: {
        [Op.lte]: priceMax,
        [Op.gte]: priceMin
      }
    };
  }
  //Every other attribute is checked separately and included in query only if it is defined for real estate type

  if (
    realEstateTypeObject.hasGardenSize &&
    gardenSizeMax != null &&
    gardenSizeMin != null
  ) {
    query.gardenSize = {
      [Op.lte]: gardenSizeMax,
      [Op.gte]: gardenSizeMin
    };
    queryIncludeIncomplete.gardenSize = {
      [Op.or]: {
        [Op.and]: {
          [Op.lte]: gardenSizeMax,
          [Op.gte]: gardenSizeMin
        },
        [Op.is]: null
      }
    };
  }

  if (
    realEstateTypeObject.hasNumberOfRoom &&
    numberOfRoomsMin != null &&
    numberOfRoomsMax != null
  ) {
    query.numberOfRooms = {
      [Op.lte]: numberOfRoomsMax,
      [Op.gte]: numberOfRoomsMin
    };
    queryIncludeIncomplete.numberOfRooms = {
      [Op.or]: {
        [Op.and]: {
          [Op.lte]: numberOfRoomsMax,
          [Op.gte]: numberOfRoomsMin
        },
        [Op.is]: null
      }
    };
  }

  if (
    realEstateTypeObject.hasNumberOfFloors &&
    numberOfFloorsMin != null &&
    numberOfFloorsMax != null
  ) {
    query.numberOfFloors = {
      [Op.lte]: numberOfFloorsMax,
      [Op.gte]: numberOfFloorsMin
    };
    queryIncludeIncomplete.numberOfFloors = {
      [Op.or]: {
        [Op.and]: {
          [Op.lte]: numberOfFloorsMax,
          [Op.gte]: numberOfFloorsMin
        },
        [Op.is]: null
      }
    };
  }

  if (
    realEstateTypeObject.hasFloorProp &&
    floorMin != null &&
    floorMax != null
  ) {
    query.floor = {
      [Op.lte]: floorMax,
      [Op.gte]: floorMin
    };
    queryIncludeIncomplete.floor = {
      [Op.or]: {
        [Op.and]: {
          [Op.lte]: floorMax,
          [Op.gte]: floorMin
        },
        [Op.is]: null
      }
    };
  }
  //Logic for balcony, newBuilding and elevator from users side
  //If true is checked, then I want characteristic to be true but,
  //if it is not checked, then I dont care - it can be null or false or true
  if (realEstateTypeObject.hasBalconyProp && balcony === true) {
    query.balcony = {
      [Op.eq]: balcony
    };
    queryIncludeIncomplete.balcony = {
      [Op.or]: {
        [Op.eq]: balcony,
        [Op.is]: null
      }
    };
  }

  if (realEstateTypeObject.hasNewBuildingProp && newBuilding === true) {
    query.newBuilding = {
      [Op.eq]: newBuilding
    };
    queryIncludeIncomplete.newBuilding = {
      [Op.or]: {
        [Op.eq]: newBuilding,
        [Op.is]: null
      }
    };
  }

  if (realEstateTypeObject.hasElevatorProp && elevator === true) {
    query.elevator = {
      [Op.eq]: elevator
    };
    queryIncludeIncomplete.elevator = {
      [Op.or]: {
        [Op.eq]: elevator,
        [Op.is]: null
      }
    };
  }
  //If user wants 'ANY' road type acces then it is not included in query -
  //returns every road type and null values
  if (accessRoadType !== "ANY") {
    query.accessRoadType = {
      [Op.eq]: accessRoadType
    };
    queryIncludeIncomplete.accessRoadType = {
      [Op.or]: {
        [Op.eq]: accessRoadType,
        [Op.is]: null
      }
    };
  }

  const order = [["updatedAt", "desc"]];

  return db.RealEstate.findAll({
    where: includeIncompleteAds ? queryIncludeIncomplete : query,
    limit: maxResults,
    order
  });
};

module.exports = {
  bulkUpsertRealEstates,
  getRealEstateById,
  findRealEstatesForSearchRequest
};
