package info.bukova.isspst;

import info.bukova.isspst.data.GlobalSettings;
import info.bukova.isspst.data.MUnit;
import info.bukova.isspst.data.NumberSeries;
import info.bukova.isspst.data.Permission;
import info.bukova.isspst.data.RequirementType;
import info.bukova.isspst.data.Role;
import info.bukova.isspst.data.Season;
import info.bukova.isspst.data.SettingsData;
import info.bukova.isspst.data.User;
import info.bukova.isspst.reporting.Report;
import info.bukova.isspst.reporting.ReportMapping;
import info.bukova.isspst.reporting.ReportType;
import info.bukova.isspst.services.dbinfo.DbInfoService;
import info.bukova.isspst.services.fulltext.FullTextService;
import info.bukova.isspst.services.munits.MUnitService;
import info.bukova.isspst.services.numberseries.NumberSeriesService;
import info.bukova.isspst.services.requirement.RequirementTypeService;
import info.bukova.isspst.services.settings.GlobalSettingsService;
import info.bukova.isspst.services.settings.SeasonException;
import info.bukova.isspst.services.settings.SeasonService;
import info.bukova.isspst.services.users.PermissionService;
import info.bukova.isspst.services.users.RoleService;
import info.bukova.isspst.services.users.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.List;

public class AppInitListener implements ServletContextListener {


	private DbInfoService dbInfoService;
	private MUnitService mUnitsService;
	private RoleService roleService;
	private UserService userService;
	private PermissionService permService;
	private NumberSeriesService nsService;
	private RequirementTypeService reqTypeService;
	private GlobalSettingsService gSettingsService;
	private FullTextService ftService;
	private SeasonService seasonService;

	@Override
	public void contextDestroyed(ServletContextEvent arg0) {

	}

	@Override
	public void contextInitialized(ServletContextEvent evt) {
		Logger logger = LoggerFactory.getLogger(AppInitListener.class);
		logger.info("Initializing database");
		
		WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(evt.getServletContext());
		dbInfoService = ctx.getBean(DbInfoService.class);
		mUnitsService = ctx.getBean(MUnitService.class);
		roleService = ctx.getBean(RoleService.class);
		userService = ctx.getBean(UserService.class);
		permService = ctx.getBean(PermissionService.class);
		nsService =ctx.getBean(NumberSeriesService.class);
		gSettingsService = ctx.getBean(GlobalSettingsService.class);
		reqTypeService = ctx.getBean(RequirementTypeService.class);
		ftService = ctx.getBean(FullTextService.class);
		seasonService = ctx.getBean(SeasonService.class);
		
		userService.grantAdmin();
		this.checkDbInfo();
		checkMUnits();
		checkRoles();
		checkUsers();
		checkPermissions();
		checkAllAdminRights();
		this.checkNumberSeries();
		checkReqTypes();
		this.checkGlobalSettings();
		buildFulltext();
		userService.removeAccess();
		
		loadModuleReports();
	}
	
	private void buildFulltext() {
		ftService.reindex();
	}
	private void checkDbInfo()
	{
		List<User> userList = userService.getAll();

		if (userList.isEmpty())
		{
			// Database is new/empty, column definition is anotated - set actual
			// database version
			dbInfoService.updateDatabaseVersion();
		}
		else
		{
			// Existing database - try change structure and set actual database
			// version...
			dbInfoService.changeDatabase();
		}
	}

	private void checkMUnits()
	{
		List<MUnit> mUnits = mUnitsService.getAll();
		
		if (mUnits.isEmpty())
		{
			MUnit mUnit = new MUnit();

			mUnit.setName("ks");
			mUnit.setDescription("kus (množství)");
			mUnitsService.add(mUnit);

			mUnit.setName("m");
			mUnit.setDescription("metr (délka)");
			mUnitsService.add(mUnit);

			mUnit.setName("km");
			mUnit.setDescription("kilometr (délka)");
			mUnitsService.add(mUnit);

			mUnit.setName("kg");
			mUnit.setDescription("kilogram (hmotnost)");
			mUnitsService.add(mUnit);

			mUnit.setName("g");
			mUnit.setDescription("gram (hmotnost)");
			mUnitsService.add(mUnit);

			mUnit.setName("dkg");
			mUnit.setDescription("dekagram (hmotnost)");
			mUnitsService.add(mUnit);

			mUnit.setName("t");
			mUnit.setDescription("tuna (hmotnost)");
			mUnitsService.add(mUnit);

			mUnit.setName("s");
			mUnit.setDescription("sekunda (čas)");
			mUnitsService.add(mUnit);

			mUnit.setName("min");
			mUnit.setDescription("minuta (čas)");
			mUnitsService.add(mUnit);

			mUnit.setName("hod");
			mUnit.setDescription("hodina (čas)");
			mUnitsService.add(mUnit);

			mUnit.setName("m²");
			mUnit.setDescription("metr čtverečný (plocha)");
			mUnitsService.add(mUnit);

			mUnit.setName("ha");
			mUnit.setDescription("hektar = 10.000 m² (plocha)");
			mUnitsService.add(mUnit);

			mUnit.setName("m³");
			mUnit.setDescription("metr krychlový (objem)");
			mUnitsService.add(mUnit);
			
			mUnit.setName("l");
			mUnit.setDescription("litr = 10E-3 m³ (objem)");
			mUnitsService.add(mUnit);
	
			mUnit.setName("A");
			mUnit.setDescription("ampér (elektrický proud)");
			mUnitsService.add(mUnit);

			mUnit.setName("K");
			mUnit.setDescription("kelvin (termodynamická teplota)");
			mUnitsService.add(mUnit);

			mUnit.setName("cd");
			mUnit.setDescription("kandela (svítivost)");
			mUnitsService.add(mUnit);

			mUnit.setName("mol");
			mUnit.setDescription("mol (látkové množství)");
			mUnitsService.add(mUnit);
		}
	}
	
	private void checkRoles() {
		for (Role role : Constants.ROLES)
		{
			if (roleService.getRoleByAuthority(role.getAuthority()) == null) {
				if (role.getAuthority().equals(Constants.ROLE_USER)) {
					role.setCentre(true);
					role.setWorkgroup(true);
				}
				roleService.add(role);
			}
		}
	}
	
	private void checkUsers() {
		boolean adminExists = false;
		
		for (User u : userService.getAll()) {
			userService.loadAuthorities(u);
			if (userService.hasRole(u, Constants.ROLE_ADMIN)) {
				adminExists = true;
				break;
			}
		}
		
		if (!adminExists) {
			User u = null;
			try {
				u = (User) userService.loadUserByUsername(Constants.DEF_ADMIN);
				u.addAuthority(roleService.getRoleByAuthority(Constants.ROLE_ADMIN));
				u.setEnabled(true);
				userService.update(u);
			} catch(UsernameNotFoundException e) {
				u = new User();
				u.setUsername(Constants.DEF_ADMIN);
				userService.setPassword(u, Constants.DEF_ADMIN_PASSWD);
				u.addAuthority(roleService.getRoleByAuthority(Constants.ROLE_ADMIN));
				u.setEnabled(true);
				userService.add(u);
			}
		}
	}
	
	private void checkPermissions() {
		for (Module m : Constants.MODULES) {
			if (!m.isDefaultPermissions()) {
				continue;
			}
			for (Permission p : Constants.DEF_PERMISSIONS) {
				if (permService.getPermissionByModule(m.getId(), p.getAuthority()) == null) {
					p.setModule(m.getId());
					permService.add(p);
				}
			}
		}
		
		for (Permission p : Constants.SPECIAL_PERMISSIONS) {
			if (permService.getPermissionByModule(p.getModule(), p.getAuthority()) == null) {
				permService.add(p);
			}
		}
	}
	
	private void checkAllAdminRights()
	{
		Role adminRole = roleService.getRoleByAuthority(Constants.ROLE_ADMIN);
		List<Permission> permissionList = adminRole.getPermissions();
		List<Permission> permissionListAvailable = permService.getAll();
		
		for (Permission permitionAvailable : permissionListAvailable)
		{
			if (!permissionList.contains(permitionAvailable))
			{
				adminRole.addPermission(permitionAvailable);
			}
		}
		
		roleService.update(adminRole);
	}
	
	private void loadModuleReports() {
		for (Module m : Constants.MODULES) {
			Report rep = new Report();
			rep.setType(ReportType.DYNAMIC);
			rep.setName(Constants.DYNAMIC_REPORT_NAME);
			m.addReport(rep);
			for (ReportMapping r : Constants.REPORTS) {
				if (m.getId().equals(r.getModule())) {
					m.addReport(r.getReport());
				}
			}
		}
	}
	
	private void checkReqTypes() {
		List<RequirementType> types = reqTypeService.getAll();
		for (RequirementType type : Constants.REQTYPES) {
			if (!types.contains(type)) {
				reqTypeService.add(type);
			}
		}
	}

	private void checkNumberSeriesForSeason(Season season) {
		for (String modId : Constants.NUM_SERIES_PREFIX.keySet()) {
			NumberSeries ns = nsService.getNumberSerieForSeason(modId, season);
			if (ns == null) {
				ns = new NumberSeries();
				ns.setModule(modId);
				ns.setPrefix(Constants.NUM_SERIES_PREFIX.get(modId));
				ns.setNumber(1);
				ns.setSeason(season);
				nsService.add(ns);
			}
		}
	}

	private void checkNumberSeries()
	{
		Season season;
		try {
			season = seasonService.getActive();
		} catch (SeasonException e) {
			season = null;
		}

		if (season == null) {
			season = new Season();
			Calendar cal = Calendar.getInstance();
			season.setDescription(String.valueOf(cal.get(Calendar.YEAR)));
			season.setValidFrom(cal.getTime());
			season.setActive(true);
			seasonService.add(season);
		}

		for (Season s : seasonService.getAllSeasons()) {
			checkNumberSeriesForSeason(s);
		}
	}
	
	private void checkGlobalSettings() {
		if (gSettingsService.getAll().isEmpty()) {
			GlobalSettings gs = new GlobalSettings();
			gSettingsService.add(gs);
		}
		
		SettingsData data = gSettingsService.getSettings();
		if (data.getRefunds().isEmpty()) {
			BigDecimal[] refunds0 = {BigDecimal.ZERO,BigDecimal.ZERO,BigDecimal.ZERO,BigDecimal.ZERO};
			BigDecimal[] refunds5 = {BigDecimal.valueOf(70),BigDecimal.valueOf(21),BigDecimal.ZERO,BigDecimal.ZERO};
			BigDecimal[] refunds12 = {BigDecimal.valueOf(105),BigDecimal.valueOf(68),BigDecimal.valueOf(32),BigDecimal.ZERO};
			BigDecimal[] refunds18 = {BigDecimal.valueOf(165),BigDecimal.valueOf(124),BigDecimal.valueOf(83),BigDecimal.valueOf(42)};
			
			data.getRefunds().put(0, refunds0);
			data.getRefunds().put(5, refunds5);
			data.getRefunds().put(12, refunds12);
			data.getRefunds().put(18, refunds18);
			
			gSettingsService.updateSettings();
		}
	}

}
