package cz.muni.fi.pa165.core;

import cz.muni.fi.pa165.core.company.Company;
import cz.muni.fi.pa165.core.company.CompanyService;
import cz.muni.fi.pa165.core.device.Device;
import cz.muni.fi.pa165.core.device.DeviceService;
import cz.muni.fi.pa165.core.house.House;
import cz.muni.fi.pa165.core.house.HouseService;
import cz.muni.fi.pa165.core.metrics.Metrics;
import cz.muni.fi.pa165.core.metrics.MetricsService;
import cz.muni.fi.pa165.core.smartmeter.SmartMeter;
import cz.muni.fi.pa165.core.smartmeter.SmartMeterService;
import cz.muni.fi.pa165.core.user.User;
import cz.muni.fi.pa165.core.user.UserService;
import cz.muni.fi.pa165.core.user.UserType;
import cz.muni.fi.pa165.core.user.roles.CompanyRole;
import cz.muni.fi.pa165.core.user.roles.HouseRole;
import cz.muni.fi.pa165.core.user.roles.Role;
import cz.muni.fi.pa165.core.user.roles.RoleService;
import cz.muni.fi.pa165.model.dto.role.enums.CompanyRoleEnum;
import cz.muni.fi.pa165.model.dto.role.enums.HouseRoleEnum;
import cz.muni.fi.pa165.model.dto.role.enums.RoleTypeEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Objects;
import java.util.logging.Logger;

import static java.time.Instant.parse;

/**
 * Class for initialing data of the core database.
 * Make sure that there are not any constraint conflicts (duped names)
 * To execute the initializer you need to have
 * <code>
 *     datainitializer:
 *          enabled: true
 * </code>
 * in the <code>application.yaml</code> or root <code>docker-compose.yaml</code>
 *
 * @author xskacel
 */

// TODO: refactoring
@RequiredArgsConstructor
@Component
@ConditionalOnProperty(name = "datainitializer.enabled")
public class DataInitializer implements ApplicationRunner {

	private static final Logger LOG = Logger.getLogger(DataInitializer.class.getName());


	private final UserService userService;
	private final DeviceService deviceService;
	private final SmartMeterService smartMeterService;
	private final MetricsService metricsService;
	private final HouseService houseService;
	private final RoleService roleService;
	private final CompanyService companyService;


	@Override
	public void run(ApplicationArguments args) {
		LOG.info("Initializing Data Initializer");
		seedAdmins();
		seedCompanies();
		seedDevices();
		seedUserWithNoHome();
		seedUserWithoutSmartMeter();
		seedUserWithTurnedOffSmartMeterAndNoMetrics();
		seedUserWithTurnedOnSmartMeterAndNoMetrics();
		seedUserWithTurnedOffSmartMeterAndMetrics();
		connectReferences(); // NEEDS TO BE LAST !!!
		LOG.info("Data Initializer Finished");
	}

	private void seedAdmins() {
		LOG.info("Seeding Admins Data");

		User admin1 = User.builder()
				.username("User9517")
				.password("q1w2e3r4t5")
				.email("User9517@hotmail.com")
				.firstName("Kaitlin")
				.lastName("Collins")
				.userType(UserType.ADMIN)
				.build();
		userService.create(admin1);

		User admin2 = User.builder()
				.username("User1739")
				.password("n8x7m6z5a4")
				.email("User1739@gmail.com")
				.firstName("Lila")
				.lastName("Campbell")
				.userType(UserType.ADMIN)
				.build();
		userService.create(admin2);
	}

	private void seedCompanies() {
		LOG.info("Seeding Company Data");

		// first company has one user (employee)
		User companyUser1 =
				User.builder()
						.email("lucy@example.com")
						.firstName("Lucy")
						.lastName("Jones")
						.username("lucyJ")
						.password("TopSecret123")
						.userType(UserType.EMPLOYEE)
						.build();
		userService.create(companyUser1);

		Company company1 = Company.builder()
				.name("BohemiaEnergy")
				.build();

		companyService.create(company1);
		company1.setCreatedAt(parse("2019-09-24T00:00:00Z"));
		companyService.getRepository().save(company1);

		CompanyRole roleForUserCompany1 = CompanyRole
				.builder()
				.company(company1)
				.companyRoleEnum(CompanyRoleEnum.Employee)
				.build();
		roleForUserCompany1.setUser(companyUser1);
		roleForUserCompany1.setRoleType(RoleTypeEnum.CompanyRole);

		company1.setEmployeeList(List.of(roleForUserCompany1));
		companyUser1.setRolesList(List.of(roleForUserCompany1));

		roleService.create(roleForUserCompany1);

		// second company has 2 users (employees)
		User companyUser2 =
				User.builder()
						.email("sam@example.com")
						.firstName("Sam")
						.lastName("Johnson")
						.username("sammyJ")
						.password("123passw0rd123")
						.userType(UserType.EMPLOYEE)
						.build();

		User companyUser3 =
				User.builder()
						.email("sara@example.com")
						.firstName("Sara")
						.lastName("Williams")
						.username("saraW")
						.password("Eassword123")
						.userType(UserType.EMPLOYEE)
						.build();
		userService.create(companyUser2);
		userService.create(companyUser3);

		Company company2 = Company.builder()
				.name("CEZ")
				.build();

		companyService.create(company2);
		company2.setCreatedAt(parse("2019-09-25T00:00:00Z"));
		companyService.getRepository().save(company2);

		CompanyRole roleForUserCompany2 = CompanyRole
				.builder()
				.company(company2)
				.companyRoleEnum(CompanyRoleEnum.Employee)
				.build();
		roleForUserCompany2.setUser(companyUser2);
		roleForUserCompany2.setRoleType(RoleTypeEnum.CompanyRole);
		companyUser2.setRolesList(List.of(roleForUserCompany2));

		CompanyRole roleForUserCompany3 = CompanyRole
				.builder()
				.company(company2)
				.companyRoleEnum(CompanyRoleEnum.Employee)
				.build();
		roleForUserCompany3.setUser(companyUser3);
		roleForUserCompany3.setRoleType(RoleTypeEnum.CompanyRole);
		companyUser3.setRolesList(List.of(roleForUserCompany3));

		company2.setEmployeeList(List.of(roleForUserCompany2, roleForUserCompany3));
		roleService.create(roleForUserCompany2);
		roleService.create(roleForUserCompany3);
	}

	private void seedDevices() {
		LOG.info("Seeding Device Data");

		// device1 is made by company1
		var company1 = companyService.findByName("BohemiaEnergy");
		Device device1 = Device
				.builder()
				.name("Bohemia Device")
				.company(company1)
				.build();

		company1.setDeviceList(List.of(device1));
		deviceService.create(device1);
		device1.setCreatedAt(parse("2021-10-24T00:00:00Z"));
		deviceService.getRepository().save(device1);
		companyService.getRepository().save(company1);

		// device2 and device3 is made by company2

		var company2 = companyService.findByName("CEZ");
		Device device2 = Device
				.builder()
				.name("CEZ Device 1.0")
				.company(company2)
				.build();

		Device device3 = Device
				.builder()
				.name("CEZ Device 1.1")
				.company(company2)
				.build();

		company2.setDeviceList(List.of(device2, device3));
		deviceService.create(device2);
		device2.setCreatedAt(parse("2021-11-24T00:00:00Z"));
		deviceService.getRepository().save(device2);
		deviceService.create(device3);
		device3.setCreatedAt(parse("2021-11-25T00:00:00Z"));
		deviceService.getRepository().save(device3);
		companyService.getRepository().save(company2);
	}

	private void seedUserWithNoHome() {
		LOG.info("Seeding User With No Home Data");


		User userWithNoHome = User.builder()
				.username("kakarot")
				.password("0dragonBalls0")
				.firstName("Son")
				.lastName("Goku")
				.email("songoku@gmail.com")
				.userType(UserType.NORMAL)
				.build();

		userService.create(userWithNoHome);
		HouseRole houseRoleForUserWithNoHome = HouseRole.builder()
				.houseRole(HouseRoleEnum.Owner)
				.build();

		houseRoleForUserWithNoHome.setRoleType(RoleTypeEnum.HouseRole);
		houseRoleForUserWithNoHome.setUser(userWithNoHome);

		roleService.create(houseRoleForUserWithNoHome);
	}

	private void seedUserWithoutSmartMeter() {
		LOG.info("Seeding User Without SmartMeter Data");

		User userWithoutSmartMeter = User.builder()
				.username("Vegito")
				.password("1superDragonBall2")
				.firstName("Gogeta")
				.lastName("Broly")
				.email("broly@email.com")
				.userType(UserType.NORMAL)
				.build();

		userService.create(userWithoutSmartMeter);
		HouseRole houseRoleForUserWithoutSmartMeter = HouseRole.builder()
				.houseRole(HouseRoleEnum.Owner)
				.build();

		houseRoleForUserWithoutSmartMeter.setRoleType(RoleTypeEnum.HouseRole);
		houseRoleForUserWithoutSmartMeter.setUser(userWithoutSmartMeter);

		roleService.create(houseRoleForUserWithoutSmartMeter);

		Company company1 = companyService.findByName("BohemiaEnergy");

		House houseWithoutSmartMeter = House.builder()
				.city("New York")
				.state("NY")
				.address("123 Main St")
				.zipcode("10001")
				.ownerList(List.of(houseRoleForUserWithoutSmartMeter))
				.build();
		houseService.create(houseWithoutSmartMeter);
		houseRoleForUserWithoutSmartMeter.setHouse(houseWithoutSmartMeter);
		roleService.getRepository().save(houseRoleForUserWithoutSmartMeter); // very ugly...
		// bohemia

	}

	private void seedUserWithTurnedOffSmartMeterAndNoMetrics() {
		LOG.info("Seeding User With Turned Off SmartMeter And No Metrics Data");

		User userWithTurnedOffSmartMeterAndNoMetrics = User.builder()
				.username("strawhat")
				.password("The0nePieceIsReal")
				.firstName("Monkey")
				.lastName("D. Luffy")
				.email("monkeydluffy@gmail.com")
				.userType(UserType.NORMAL)
				.build();

		userService.create(userWithTurnedOffSmartMeterAndNoMetrics);
		HouseRole houseRoleForUserWithTurnedOffSmartMeterAndNoMetrics = HouseRole.builder()
				.houseRole(HouseRoleEnum.Owner)
				.build();

		houseRoleForUserWithTurnedOffSmartMeterAndNoMetrics.setRoleType(RoleTypeEnum.HouseRole);
		houseRoleForUserWithTurnedOffSmartMeterAndNoMetrics.setUser(userWithTurnedOffSmartMeterAndNoMetrics);

		roleService.create(houseRoleForUserWithTurnedOffSmartMeterAndNoMetrics);

		House houseWithTurnedOffSmartMeterAndNoMetrics = House.builder()
				.city("Los Angeles")
				.state("California")
				.address("123 Test St.")
				.zipcode("33322")
				.ownerList(List.of(houseRoleForUserWithTurnedOffSmartMeterAndNoMetrics))
				.build();
		houseService.create(houseWithTurnedOffSmartMeterAndNoMetrics);
		houseRoleForUserWithTurnedOffSmartMeterAndNoMetrics.setHouse(houseWithTurnedOffSmartMeterAndNoMetrics);
		roleService.getRepository().save(houseRoleForUserWithTurnedOffSmartMeterAndNoMetrics); // very ugly...

		//Company company1 = companyService.findByName("BohemiaEnergy");
		//company1.setHouseList(List.of(houseWithTurnedOffSmartMeterAndNoMetrics)); // should be done as a last thing
		//companyService.getRepository().save(company1);

		Device device1 = deviceService.findByName("Bohemia Device");
		SmartMeter turnedOffSmartMeterWithNoMetrics = SmartMeter.builder()
				.device(device1)
				.house(houseWithTurnedOffSmartMeterAndNoMetrics)
				.name("Bohemia Device Kitchen")
				.active(false)
				.build();

		smartMeterService.create(turnedOffSmartMeterWithNoMetrics);
		//device1.setSmartMeterList(List.of(turnedOffSmartMeterWithNoMetrics));
		//deviceService.getRepository().save(device1);
	}

	private void seedUserWithTurnedOnSmartMeterAndNoMetrics() {
		LOG.info("Seeding User With Turned On SmartMeter And No Metrics Data");

		User userWithTurnedOnSmartMeterAndNoMetrics = User.builder()
				.username("akagami")
				.password("devine0Departure")
				.firstName("RedHair")
				.lastName("Shanks")
				.email("redhairshanks@gmail.com")
				.userType(UserType.NORMAL)
				.build();

		userService.create(userWithTurnedOnSmartMeterAndNoMetrics);
		userWithTurnedOnSmartMeterAndNoMetrics.setCreatedAt(parse("2022-11-28T00:00:00Z"));
		userService.getRepository().save(userWithTurnedOnSmartMeterAndNoMetrics);

		HouseRole houseRoleForUserWithTurnedOnSmartMeterAndNoMetrics = HouseRole.builder()
				.houseRole(HouseRoleEnum.Owner)
				.build();

		houseRoleForUserWithTurnedOnSmartMeterAndNoMetrics.setRoleType(RoleTypeEnum.HouseRole);
		houseRoleForUserWithTurnedOnSmartMeterAndNoMetrics.setUser(userWithTurnedOnSmartMeterAndNoMetrics);
		roleService.create(houseRoleForUserWithTurnedOnSmartMeterAndNoMetrics);
		houseRoleForUserWithTurnedOnSmartMeterAndNoMetrics.setCreatedAt(parse("2022-11-28T00:00:00Z"));
		roleService.getRepository().save(houseRoleForUserWithTurnedOnSmartMeterAndNoMetrics);

		House houseWithTurnedOnSmartMeterAndNoMetrics = House.builder()
				.city("Prague")
				.state("CZ")
				.address("231 Golden St.")
				.zipcode("888 22")
				.ownerList(List.of(houseRoleForUserWithTurnedOnSmartMeterAndNoMetrics))
				.build();

		houseService.create(houseWithTurnedOnSmartMeterAndNoMetrics);
		houseWithTurnedOnSmartMeterAndNoMetrics.setCreatedAt(parse("2022-11-28T00:01:00Z"));
		houseService.getRepository().save(houseWithTurnedOnSmartMeterAndNoMetrics);
		houseRoleForUserWithTurnedOnSmartMeterAndNoMetrics.setHouse(houseWithTurnedOnSmartMeterAndNoMetrics);
		roleService.getRepository().save(houseRoleForUserWithTurnedOnSmartMeterAndNoMetrics); // very ugly...

		// Company company1 = companyService.findByName("BohemiaEnergy");
		// company1.getHouseList().add(houseWithTurnedOnSmartMeterAndNoMetrics);
		// companyService.getRepository().save(company1);

		Device device1 = deviceService.findByName("Bohemia Device");
		SmartMeter turnedOnSmartMeterWithNoMetrics = SmartMeter.builder()
				.device(device1)
				.house(houseWithTurnedOnSmartMeterAndNoMetrics)
				.name("Bohemia Device Bathroom")
				.active(true)
				.build();

		smartMeterService.create(turnedOnSmartMeterWithNoMetrics);
		// device1.getSmartMeterList().add(turnedOnSmartMeterWithNoMetrics);
		// deviceService.getRepository().save(device1);
	}

	private void seedUserWithTurnedOffSmartMeterAndMetrics() {
		LOG.info("Seeding User With Turned Off SmartMeter And Metrics Data");

		User userWithTurnedOffSmartMeterAndMetrics = User.builder()
				.username("PiccoroJr")
				.password("dragonBallIsG0AT")
				.firstName("piccoro")
				.lastName("junior")
				.email("piccoro@gmail.com")
				.userType(UserType.NORMAL)
				.build();

		userService.create(userWithTurnedOffSmartMeterAndMetrics);
		userWithTurnedOffSmartMeterAndMetrics.setCreatedAt(parse("2023-01-01T00:00:00Z"));
		userService.getRepository().save(userWithTurnedOffSmartMeterAndMetrics);

		HouseRole houseRoleForUserWithTurnedOffSmartMeterAndMetrics = HouseRole.builder()
				.houseRole(HouseRoleEnum.Owner)
				.build();

		houseRoleForUserWithTurnedOffSmartMeterAndMetrics.setCreatedAt(parse("2023-01-01T00:01:00Z"));
		houseRoleForUserWithTurnedOffSmartMeterAndMetrics.setRoleType(RoleTypeEnum.HouseRole);
		houseRoleForUserWithTurnedOffSmartMeterAndMetrics.setUser(userWithTurnedOffSmartMeterAndMetrics);
		roleService.create(houseRoleForUserWithTurnedOffSmartMeterAndMetrics);
		houseRoleForUserWithTurnedOffSmartMeterAndMetrics.setCreatedAt(parse("2023-01-01T00:01:00Z"));
		roleService.getRepository().save(houseRoleForUserWithTurnedOffSmartMeterAndMetrics);
		House houseWithTurnedOffSmartMeterAndMetrics = House.builder()
				.city("Berlin")
				.state("GERMANY")
				.address("333 Schnitzel St.")
				.zipcode("111 22")
				.ownerList(List.of(houseRoleForUserWithTurnedOffSmartMeterAndMetrics))
				.build();
		houseService.create(houseWithTurnedOffSmartMeterAndMetrics);
		houseWithTurnedOffSmartMeterAndMetrics.setCreatedAt(parse("2023-01-01T00:01:00Z"));
		houseService.getRepository().save(houseWithTurnedOffSmartMeterAndMetrics); // very ugly...
		houseRoleForUserWithTurnedOffSmartMeterAndMetrics.setHouse(houseWithTurnedOffSmartMeterAndMetrics);
		roleService.getRepository().save(houseRoleForUserWithTurnedOffSmartMeterAndMetrics); // very ugly...

		Device device3 = deviceService.findByName("CEZ Device 1.1");
		SmartMeter turnedOffSmartMeterWithMetrics = SmartMeter.builder()
				.device(device3)
				.house(houseWithTurnedOffSmartMeterAndMetrics)
				.name("CEZ Device 1.1 Kitchen")
				.active(false)
				.build();
		turnedOffSmartMeterWithMetrics.setCreatedAt(parse("2023-01-01T00:03:00Z"));


		smartMeterService.create(turnedOffSmartMeterWithMetrics);

		Metrics metrics1 = new Metrics(13.234, turnedOffSmartMeterWithMetrics);
		Metrics metrics2 = new Metrics(20.134, turnedOffSmartMeterWithMetrics);
		Metrics metrics3 = new Metrics(24.124, turnedOffSmartMeterWithMetrics);
		Metrics metrics4 = new Metrics(89.255, turnedOffSmartMeterWithMetrics);


		List.of(metrics1, metrics2, metrics3, metrics4).forEach(metricsService::create);
		metrics1.setCreatedAt(parse("2023-01-01T00:05:00Z"));
		metrics2.setCreatedAt(parse("2023-01-01T00:06:00Z"));
		metrics3.setCreatedAt(parse("2023-01-01T00:08:00Z"));
		metrics4.setCreatedAt(parse("2023-01-01T00:09:00Z"));
		List.of(metrics1, metrics2, metrics3, metrics4).forEach(metrics -> metricsService.getRepository().save(metrics));
		turnedOffSmartMeterWithMetrics.setMetricsList(List.of(metrics1, metrics2, metrics3, metrics4));

		//device1.setSmartMeterList(List.of(turnedOffSmartMeterWithNoMetrics));
		//deviceService.getRepository().save(device1);
	}

	private void connectReferences() {
		LOG.info("Connecting References (foreign keys) Started");

		Company company1 = companyService.findByName("BohemiaEnergy");
		Company company2 = companyService.findByName("CEZ");
		var houseWithoutSmartMeter = houseService.findAll()
				.stream()
				.filter(house -> Objects.equals(house.getAddress(), "123 Main St"))
				.findFirst()
				.orElseThrow(RuntimeException::new);
		var houseWithTurnedOffSmartMeterAndNoMetrics = houseService.findAll()
				.stream()
				.filter(house -> Objects.equals(house.getAddress(), "123 Test St."))
				.findFirst()
				.orElseThrow(RuntimeException::new);
		var houseWithTurnedOnSmartMeterAndNoMetrics = houseService.findAll()
				.stream()
				.filter(house -> Objects.equals(house.getAddress(), "231 Golden St."))
				.findFirst()
				.orElseThrow(RuntimeException::new);
		var houseWithTurnedOffSmartMeterAndMetrics = houseService.findAll()
				.stream()
				.filter(house -> Objects.equals(house.getAddress(), "333 Schnitzel St."))
				.findFirst()
				.orElseThrow(RuntimeException::new);


		company1.setHouseList(List.of(houseWithoutSmartMeter, houseWithTurnedOffSmartMeterAndNoMetrics, houseWithTurnedOnSmartMeterAndNoMetrics));
		companyService.getRepository().save(company1);
		company2.setHouseList(List.of(houseWithTurnedOffSmartMeterAndMetrics));
		companyService.getRepository().save(company2);

		Device device1 = deviceService.findByName("Bohemia Device");
		Device device2 = deviceService.findByName("CEZ Device 1.0");
		Device device3 = deviceService.findByName("CEZ Device 1.1");

		var turnedOffSmartMeterWithNoMetrics = smartMeterService.findByName("Bohemia Device Kitchen");
		var turnedOnSmartMeterWithNoMetrics = smartMeterService.findByName("Bohemia Device Bathroom");
		var turnedOffSmartMeterWithMetrics = smartMeterService.findByName("CEZ Device 1.1 Kitchen");

		device1.setSmartMeterList(List.of(turnedOffSmartMeterWithNoMetrics, turnedOnSmartMeterWithNoMetrics));
		deviceService.getRepository().save(device1);
		device3.setSmartMeterList(List.of(turnedOffSmartMeterWithMetrics));
		deviceService.getRepository().save(device3);
	}
}
