/* © SRSoftware 2025 */
package de.srsoftware.umbrella.company;

import static de.srsoftware.umbrella.company.Constants.CONFIG_DATABASE;
import static de.srsoftware.umbrella.company.Constants.NEXT_CUSTOMER_NUMBER;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.ModuleRegistry.*;
import static de.srsoftware.umbrella.core.Util.mapValues;
import static de.srsoftware.umbrella.core.constants.Field.*;
import static de.srsoftware.umbrella.core.constants.Path.LIST;
import static de.srsoftware.umbrella.core.constants.Path.SEARCH;
import static de.srsoftware.umbrella.core.constants.Text.COMPANY_WITH_ID;
import static de.srsoftware.umbrella.core.constants.Text.LONG;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.model.Translatable.t;

import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.configuration.Configuration;
import de.srsoftware.tools.Path;
import de.srsoftware.tools.SessionToken;
import de.srsoftware.umbrella.company.api.CompanyDb;
import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.ModuleRegistry;
import de.srsoftware.umbrella.core.api.CompanyService;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.*;
import java.io.IOException;
import java.util.*;

public class CompanyModule extends BaseHandler implements CompanyService {

	private final CompanyDb companyDb;

	public CompanyModule(Configuration config) throws UmbrellaException {
		super();
		var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingField(CONFIG_DATABASE));
		companyDb = new SqliteDb(connect(dbFile));
		ModuleRegistry.add(this);
	}

	private boolean deleteCompany(long companyId, UmbrellaUser user, HttpExchange ex) throws IOException {
		var company = get(companyId);
		if (!membership(companyId,user.id())) throw forbidden("You are mot a member of company {company}", COMPANY,company.name());
		if (!documentService().list(companyId).isEmpty()) throw forbidden("There are documents owned by {company}", COMPANY,company.name());
		if (!itemService().getCompanyItems(companyId).isEmpty()) throw forbidden("There are items owned by {company}", COMPANY,company.name());
		if (!projectService().listCompanyProjects(companyId,true).isEmpty()) throw forbidden("There are projects owned by {company}", COMPANY,company.name());
		return sendContent(ex, companyDb.drop(companyId));
	}

	@Override
	public boolean doDelete(Path path, HttpExchange ex) throws IOException {
		addCors(ex);
		try {
			Optional<Token> token = SessionToken.from(ex).map(Token::of);
			var user = userService().loadUser(token);
			if (user.isEmpty()) return unauthorized(ex);
			var head = path.pop();
			return switch (head) {
				case null -> super.doDelete(path, ex);
				default -> deleteCompany(Long.parseLong(head), user.get(), ex);
			};
		} catch (NumberFormatException n) {
			return send(ex,invalidField(ID,t(LONG)));
		} catch (UmbrellaException e) {
			return send(ex,e);
		}
	}

	@Override
	public boolean doGet(Path path, HttpExchange ex) throws IOException {
		addCors(ex);
		try {
			Optional<Token> token = SessionToken.from(ex).map(Token::of);
			var user = userService().loadUser(token);
			if (user.isEmpty()) return unauthorized(ex);
			var head = path.pop();
			return switch (head){
				case LIST -> getCompanyList(user.get(),ex);
				case null,
				default -> {
					try {
						yield getCompany(user.get(), path, Long.parseLong(head), ex);
					} catch (NumberFormatException ignored){}
					yield super.doGet(path,ex);
				}
			};
		} catch (UmbrellaException e) {
			return send(ex,e);
		}
	}

	@Override
	public boolean doPatch(Path path, HttpExchange ex) throws IOException {
		addCors(ex);
		try {
			Optional<Token> token = SessionToken.from(ex).map(Token::of);
			var user = userService().loadUser(token);
			if (user.isEmpty()) return unauthorized(ex);
			var head = path.pop();
			return switch (head) {
				case null -> super.doPatch(path, ex);
				default -> patchCompany(Long.parseLong(head), user.get(), ex);
			};
		} catch (NumberFormatException n) {
			return send(ex,invalidField(ID,t(LONG)));
		} catch (UmbrellaException e) {
			return send(ex,e);
		}
	}

	@Override
	public boolean doPost(Path path, HttpExchange ex) throws IOException {
		addCors(ex);
		try {
			Optional<Token> token = SessionToken.from(ex).map(Token::of);
			var user = userService().loadUser(token);
			if (user.isEmpty()) return unauthorized(ex);
			var head = path.pop();
			return switch (head) {
				case SEARCH -> postSearch(user.get(),ex);
				case null -> postNewCompany(user.get(),ex);
				default -> super.doPost(path,ex);
			};
		} catch (NumberFormatException n) {
			return send(ex,invalidField(ID,t(LONG)));
		} catch (UmbrellaException e) {
			return send(ex,e);
		}
	}

	@Override
	public Company get(long companyId) throws UmbrellaException {
		return companyDb.load(companyId);
	}

	private boolean getCompany(UmbrellaUser user, Path path, long companyId, HttpExchange ex) throws IOException {
		return switch (path.pop()){
			case NEXT_CUSTOMER_NUMBER -> getNextCustomerNumber(user, companyId, ex);
			case null, default -> super.doGet(path,ex);
		};

	}

	private boolean getCompanyList(UmbrellaUser user, HttpExchange ex) throws IOException, UmbrellaException {
		var result = new HashMap<Long,Map<String,Object>>();
		for (var entry : listCompaniesOf(user).entrySet()) result.put(entry.getKey(),entry.getValue().toMap());
		return sendContent(ex,result);
	}

	@Override
	public Collection<UmbrellaUser> getMembers(long companyId) throws UmbrellaException {
		var members = new HashSet<UmbrellaUser>();
		for (var userId : companyDb.getMembers(companyId)) members.add(userService().loadUser(userId));
		return members;
	}

	private boolean getNextCustomerNumber(UmbrellaUser user, long companyId, HttpExchange ex) throws IOException {
		var company = companyDb.load(companyId);
		if (!membership(companyId,user.id())) throw notAmember(t(COMPANY_WITH_ID,ID,company.name()));
		var nextCustomerNumber = companyDb.getNextCustomerNumber(companyId);
		return sendContent(ex,nextCustomerNumber);
	}

	@Override
	public Map<Long,Company> listCompaniesOf(UmbrellaUser user) throws UmbrellaException {
		return loadMembers(companyDb.listCompaniesOf(user.id()));
	}

	public Map<Long,Company> loadMembers(Map<Long,Company> companyMap){
		loadMembers(companyMap.values());
		return companyMap;
	}

	public Collection<Company> loadMembers(Collection<Company> companyList) {
		var userMap = new HashMap<Long,UmbrellaUser>();
		for (var company : companyList){
			for (var userId : companyDb.getMembers(company.id())){
				var user = userMap.computeIfAbsent(userId,k -> userService().loadUser(userId));
				company.members().put(userId,user);
			}
		}
		return companyList;
	}

	@Override
	public boolean membership(long companyId, long userId) throws UmbrellaException {
		return companyDb.getMembers(companyId).contains(userId);
	}

	private boolean patchCompany(long companyId, UmbrellaUser user, HttpExchange ex) throws IOException {
		var company = get(companyId);
		if (!membership(companyId,user.id())) throw notAmember(t(COMPANY_WITH_ID,ID,company.name()));

		var json = json(ex);
		company = companyDb.save(company.patch(json));
		if (json.has(MEMBERS)){
			var wantedMembers = new HashSet<Long>();
			wantedMembers.add(user.id()); // user may not delete itself
			for (var o : json.getJSONArray(MEMBERS).toList()){
				if (o instanceof Number uid) wantedMembers.add(uid.longValue());
			}
			loadMembers(List.of(company)); // load current members
			var currentMembers = company.members().keySet();
			for (var currentMember : currentMembers){
				if (!wantedMembers.contains(currentMember)) companyDb.dropUser(companyId,currentMember);
			}
			for (var wantedMember : wantedMembers){
				if (!currentMembers.contains(wantedMember)) companyDb.addUser(companyId,wantedMember);
			}
			company.members().clear();
			loadMembers(List.of(company)); // load new members
		}

		return sendContent(ex,company);
	}

	private boolean postNewCompany(UmbrellaUser user, HttpExchange ex) throws IOException {
		var json = json(ex);
		json.put(ID,0);
		var company = companyDb.save(Company.of(json));
		companyDb.addUser(company.id(),user.id());
		return sendContent(ex,company);
	}

	private boolean postSearch(UmbrellaUser user, HttpExchange ex) throws IOException {
		var json = json(ex);
		if (!(json.has(KEY) && json.get(KEY) instanceof String key)) throw missingField(KEY);
		var keys = Arrays.asList(key.split(" "));
		var companies = companyDb.find(user,keys);
		return sendContent(ex,mapValues(companies));
	}

	@Override
	public void saveNewCustomer(long companyId, String id) {
		companyDb.saveNewCustomer(companyId, id);
	}
}
