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

import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_UNPROCESSABLE;
import static de.srsoftware.umbrella.core.constants.Field.USER_LIST;
import static de.srsoftware.umbrella.core.constants.Path.SEARCH;
import static de.srsoftware.umbrella.core.constants.Path.USES;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.tags.Constants.*;

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.core.BaseHandler;
import de.srsoftware.umbrella.core.ModuleRegistry;
import de.srsoftware.umbrella.core.api.TagService;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Token;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.io.IOException;
import java.util.*;
import org.json.JSONArray;

public class TagModule extends BaseHandler implements TagService {
	private final TagDB tagDb;

	public TagModule(Configuration config) {
		super();
		var tagDbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingField(CONFIG_DATABASE));
		var bmDbFile = config.get(de.srsoftware.umbrella.bookmarks.Constants.CONFIG_DATABASE).orElseThrow(() -> missingField(de.srsoftware.umbrella.bookmarks.Constants.CONFIG_DATABASE));
		tagDb      = new SqliteDb(connect(tagDbFile),connect(bmDbFile));
		ModuleRegistry.add(this);
	}

	@Override
	public void deleteEntity(String module, long entityId) {
		tagDb.deleteEntity(module,entityId);
	}

	@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 module = path.pop();
			if (module == null) throw unprocessable("Module missing in path.");
			var head = path.pop();
			long entityId = Long.parseLong(head);
			var tag = tagDb.delete(user.get().id(),module,entityId,body(ex));
			return sendContent(ex, tag);
		} catch (NumberFormatException e){
			return sendContent(ex,HTTP_UNPROCESSABLE,"Entity id missing in path.");
		} catch (UmbrellaException e){
			return send(ex,e);
		}
	}

	@Override
	public boolean doGet(Path path, HttpExchange ex) throws IOException {
		addCors(ex);
		try {
			var user = userService().refreshSession(ex);
			if (user.isEmpty()) return unauthorized(ex);
			var module = path.pop();
			return switch (module){
				case SEARCH -> searchTags(ex,path.pop(),user.get());
				case USES -> getTagUses(ex,path.pop(),user.get());
				case null -> getUserTags(ex, user.get());
				default -> sendContent(ex, getTags(module,Long.parseLong(path.pop()),user.get()));
			};

		} catch (NumberFormatException e){
			return sendContent(ex,HTTP_UNPROCESSABLE,"Entity id missing in path.");
		} 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 module = path.pop();
			if (module == null) throw unprocessable("Module missing in path.");
			var head = path.pop();
			long entityId = Long.parseLong(head);
			var json = json(ex);
			if (!(json.has(TAG) && json.get(TAG) instanceof String tag && !tag.isBlank())) throw missingField(TAG);
			List<Long> userList = null;
			if (!json.has(USER_LIST)) throw missingField(USER_LIST);
			var ul = json.isNull(USER_LIST) ? null : json.get(USER_LIST);
			if (ul instanceof JSONArray arr){
				userList = arr.toList().stream()
						.filter(elem -> elem instanceof Number)
						.map(Number.class::cast)
						.map(Number::longValue)
						.toList();
			} else if (ul != null) throw unprocessable("User list must be NULL or array of user ids!");
			// userList == null → tag is shared with all users
			if (userList != null && userList.isEmpty()) throw unprocessable("User list must not be an empty list!");
			tag = save(module, entityId, userList, tag);
			return sendContent(ex, tag);
		} catch (NumberFormatException e) {
			return sendContent(ex, HTTP_UNPROCESSABLE, "Entity id missing in path.");
		} catch (UmbrellaException e) {
			return send(ex, e);
		}
	}

	public Map<String, List<Long>> getTagUses(UmbrellaUser user, String tag){
		return tagDb.getUses(tag,user.id());
	}

	private boolean getTagUses(HttpExchange ex, String tag, UmbrellaUser user) throws IOException {
		return sendContent(ex,getTagUses(user,tag));
	}

	public Collection<String> getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException{
		return tagDb.list(user.id(),module,entityId);
	}

	@Override
	public Map<Long, ? extends Collection<String>> getTags(String module, Collection<Long> entityIds, UmbrellaUser user) throws UmbrellaException {
		return tagDb.list(user.id(),module,entityIds);
	}

	private boolean getUserTags(HttpExchange ex, UmbrellaUser user) throws IOException {
		var tuples = tagDb.list(user.id()).stream().filter(t -> t.a != null && t.b != null);
		return sendContent(ex,tuples.map(t -> Map.of(TAG,t.a,COUNT,t.b)));
	}

	@Override
	public void deleteEntity(String module, long entityId, long userId) {
		tagDb.deleteEntity(module,entityId,userId);
	}

	@Override
	public void save(String module, long entityId, Collection<Long> userIds, Collection<String> tags) {
		tagDb.save(userIds,module,entityId,tags.stream().map(String::trim).toList());
	}

	@Override
	public String save(String module, long entityId, Collection<Long> userIds, String tag) {
		save(module,entityId,userIds,List.of(tag));
		return tag;
	}

	@Override
	public Collection<String> search(String key, UmbrellaUser user) {
		return tagDb.search(key, user);
	}

	private boolean searchTags(HttpExchange ex, String head, UmbrellaUser user) throws IOException {
		return sendContent(ex, search(head, user));
	}

	@Override
	public void updateId(String module, Object oldId, Object newId) {
		tagDb.updateId(module,oldId,newId);
	}
}
