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

import static de.srsoftware.umbrella.bookmarks.Constants.*;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.ModuleRegistry.tagService;
import static de.srsoftware.umbrella.core.Util.mapValues;
import static de.srsoftware.umbrella.core.constants.Field.*;
import static de.srsoftware.umbrella.core.constants.Field.TAGS;
import static de.srsoftware.umbrella.core.constants.Module.BOOKMARK;
import static de.srsoftware.umbrella.core.constants.Path.LIST;
import static de.srsoftware.umbrella.core.constants.Path.SEARCH;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus;
import static de.srsoftware.umbrella.messagebus.events.Event.EventType.CREATE;
import static de.srsoftware.umbrella.messagebus.events.Event.EventType.UPDATE;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_OK;

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.BookmarkService;
import de.srsoftware.umbrella.core.constants.Text;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Token;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import de.srsoftware.umbrella.messagebus.events.BookmarkEvent;
import java.io.IOException;
import java.util.*;
import org.json.JSONArray;

public class BookmarkApi extends BaseHandler implements BookmarkService {
	private final BookmarkDb db;

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

	private boolean deleteBookmark(UmbrellaUser user, HttpExchange ex, long urlId) throws IOException {
		var bookmark = db.load(urlId,user.id());
		db.remove(user, bookmark);
		return sendEmptyResponse(HTTP_OK,ex);
	}

	@Override
	public boolean doDelete(Path path, HttpExchange ex) throws IOException {
		addCors(ex);
		try {
			Optional<Token> token = SessionToken.from(ex).map(Token::of);
			var user = ModuleRegistry.userService().loadUser(token);
			if (user.isEmpty()) return unauthorized(ex);
			var head = path.pop();
			if (head == null) throw missingField(ID);
			var id = Long.parseLong(head);
			return deleteBookmark(user.get(),ex,id);
		} catch (NumberFormatException e){
			throw invalidField(ID, Text.NUMBER);
		} 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 = ModuleRegistry.userService().loadUser(token);
			if (user.isEmpty()) return unauthorized(ex);
			var head = path.pop();
			return switch (head) {
				case LIST -> getUserBookmarks(user.get(),ex);
				case null -> super.doPost(path,ex);
				default -> {
					var urlId = Long.parseLong(head);
					yield getBookmark(user.get(),urlId,ex);
				}
			};
		} catch (NumberFormatException e){
			throw invalidField(ID, Text.NUMBER);
		} 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 = ModuleRegistry.userService().loadUser(token);
			if (user.isEmpty()) return unauthorized(ex);
			var head = path.pop();
			if (head == null) throw missingField(ID);
			var id = Long.parseLong(head);
			return patchBookmark(user.get(),ex,id);
		} catch (NumberFormatException e){
			throw invalidField(ID, Text.NUMBER);
		} 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 = ModuleRegistry.userService().loadUser(token);
			if (user.isEmpty()) return unauthorized(ex);
			var head = path.pop();
			return switch (head) {
				case SEARCH -> postSearch(user.get(),ex);
				case null -> postBookmark(user.get(),ex);
				default -> super.doPost(path,ex);
			};
		} catch (NumberFormatException e){
			return sendContent(ex,HTTP_BAD_REQUEST,"Invalid project id");
		} catch (UmbrellaException e){
			return send(ex,e);
		}
	}

	@Override
	public Map<Long, String> find(String key) {
		return db.findUrls(key);
	}

	private boolean getBookmark(UmbrellaUser user, long urlId, HttpExchange ex) throws IOException {
		var bookmark = db.load(urlId,user.id());
		tagService().getTags(BOOKMARK, urlId, user).forEach(bookmark.tags()::add);
		return sendContent(ex,bookmark);
	}

	private boolean getUserBookmarks(UmbrellaUser user, HttpExchange ex) throws IOException {
		var param = queryParam(ex);
		long offset = switch (param.get(OFFSET)){
			case String s -> Long.parseLong(s);
			case Number n -> n.longValue();
			case null, default -> 0;
		};
		long limit = switch (param.get(LIMIT)){
			case String s -> Long.parseLong(s);
			case Number n -> n.longValue();
			case null, default -> 100;
		};

		var bookmarks = db.list(user.id(),offset,limit);
		return sendContent(ex,mapValues(bookmarks));
	}

	private boolean patchBookmark(UmbrellaUser user, HttpExchange ex, long urlId) throws IOException {
		var bookmark = db.load(urlId,user.id());
		var tags     = tagService().getTags(BOOKMARK,urlId,user);
		var json     = json(ex);
		var comment  = bookmark.comment();
		var url      = bookmark.url();
		if (json.has(COMMENT) && json.get(COMMENT) instanceof String c) comment = c;
		if (json.has(URL) && json.get(URL) instanceof String u)         url     = u;
		var newBookmark = db.save(url,comment, List.of(user.id()),bookmark.timestamp());
		if (newBookmark.urlId() != urlId) {
			tagService().save(BOOKMARK,newBookmark.urlId(),List.of(user.id()),tags);
			db.remove(user, bookmark);
			messageBus().dispatch(new BookmarkEvent(user,newBookmark,CREATE));
		} else messageBus().dispatch(new BookmarkEvent(user,newBookmark,UPDATE));
		return sendContent(ex,newBookmark);
	}

	private boolean postBookmark(UmbrellaUser user, HttpExchange ex) throws IOException {
		var json = json(ex);
		if (!(json.has(URL) && json.get(URL) instanceof String url)) throw missingField(URL);
		if (!(json.has(COMMENT) && json.get(COMMENT) instanceof String comment)) throw missingField(COMMENT);
		var userList = new ArrayList<Long>();
		userList.add(user.id());
		if (json.has(SHARE) && json.get(SHARE) instanceof JSONArray arr){
			for (Object o : arr.toList()) {
				if (!(o instanceof Number uid)) throw invalidField(SHARE,"Array of ids");
				userList.add(uid.longValue());
			}
		}
		var bookmark = db.save(url,comment, userList);
		messageBus().dispatch(new BookmarkEvent(user,bookmark,CREATE));

		if (json.has(TAGS) && json.get(TAGS) instanceof JSONArray tagList){
			var list = tagList.toList().stream().map(Object::toString).toList();
			tagService().save(BOOKMARK,bookmark.urlId(), userList, list);
		}
		return sendContent(ex,bookmark);
	}

	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 fulltext = json.has(FULLTEXT) && json.get(FULLTEXT) instanceof Boolean val && val;
		var bookmarks = db.findUrls(user.id(),keys);
		return sendContent(ex,mapValues(bookmarks));
	}
}
