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

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.Util.mapValues;
import static de.srsoftware.umbrella.core.constants.Field.*;
import static de.srsoftware.umbrella.core.constants.Path.SEARCH;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.notes.Constants.CONFIG_DATABASE;

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.NoteService;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Note;
import de.srsoftware.umbrella.core.model.Token;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class NoteModule extends BaseHandler implements NoteService {
	private final NotesDb notesDb;

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

	@Override
	public void deleteEntity(String module, String entityId) {
		notesDb.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 head = path.pop();
			if (head == null) throw unprocessable("Module missing in path.");
//			try {
				var noteId = Long.parseLong(head);
				var note = notesDb.load(noteId);
				if (note.authorId() != user.get().id()) throw forbidden("You are not allowed to delete notes of another user");
				return sendContent(ex, notesDb.delete(noteId));
/*			} catch (NumberFormatException ignored) {
				var module = head;
				head = path.pop();
				long entityId = Long.parseLong(head);
				notesDb.deleteEntity(module,entityId);
				return sendContent(ex, entityId);
			}*/
		} 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 null -> sendContent(ex,getUserNotes(ex,user.get()));
				default -> sendContent(ex,getEntityNotes(module,path.pop()));
			};
		} catch (NumberFormatException e){
			return sendContent(ex,HTTP_UNPROCESSABLE,"Entity id missing in path.");
		} catch (UmbrellaException e){
			return send(ex,e);
		}
	}

	private boolean getUserNotes(HttpExchange ex, UmbrellaUser user) 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 notes = notesDb.list(user.id(),offset,limit);
		return sendContent(ex,addUsers(notes));
	}

	private Map<String, Object> addUsers(Map<Long, Note> notes) {
		var authors = notes.values().stream().map(Note::authorId).distinct().map(userService()::loadUser).collect(Collectors.toMap(UmbrellaUser::id,UmbrellaUser::toMap));
		return Map.of("notes",mapValues(notes),"authors",authors);
	}

	@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().refreshSession(ex);
			if (user.isEmpty()) return unauthorized(ex);
			var head = path.pop();
			long noteId = Long.parseLong(head);
			String text = body(ex);
			if (text.isBlank()) throw missingField("Note text");
			var note = notesDb.load(noteId);
			if (note.authorId() != user.get().id()) throw forbidden("You are not allowed to edit notes of another user!");
			note = new Note(note.id(),note.module(),note.entityId(),note.authorId(),text,LocalDateTime.now());
			note = save(note);
			return sendContent(ex,note);
		} 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 (SEARCH.equals(module)) return postSearch(ex,user.get());
			if (module == null) throw unprocessable("Module missing in path.");
			var entityId = path.pop();
			if (entityId == null || entityId.isBlank()) throw missingField(ENTITY_ID);
			String text = body(ex);
			if (text.isBlank()) throw missingField("Note text");
			var note = new Note(0,module,entityId,user.get().id(),text, LocalDateTime.now());
			note = save(note);
			return sendContent(ex, note);
		} catch (NumberFormatException e) {
			return sendContent(ex, HTTP_UNPROCESSABLE, "Entity id missing in path.");
		} catch (UmbrellaException e) {
			return send(ex, e);
		}
	}

	public Map<String,Object> getEntityNotes(String module, String entityId) throws UmbrellaException{
		return addUsers(getNotes(module, entityId));
	}

	@Override
	public Map<Long, Note> getNotes(String module, String entityId) throws UmbrellaException {
		return notesDb.list(module, entityId);
	}

	private boolean postSearch(HttpExchange ex, UmbrellaUser user) 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 notes = notesDb.find(user.id(),keys,fulltext);
		return sendContent(ex,mapValues(notes));
	}

	@Override
	public Note save(Note note) {
		return notesDb.save(note);
	}

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