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

import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
import static de.srsoftware.umbrella.core.Util.mapValues;
import static de.srsoftware.umbrella.core.constants.Field.*;
import static de.srsoftware.umbrella.core.constants.Path.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.model.Permission.EDIT;
import static de.srsoftware.umbrella.core.model.Permission.READ_ONLY;
import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus;
import static de.srsoftware.umbrella.wiki.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.api.WikiService;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.*;
import de.srsoftware.umbrella.messagebus.events.WikiEvent;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;

public class WikiModule extends BaseHandler implements WikiService {
	private final WikiDb wikiDb;

	public WikiModule(Configuration config) {
		super();
		var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingField(CONFIG_DATABASE));
		wikiDb     = new SqliteDb(connect(dbFile));
	}


	private boolean deletePage(Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
		var id = path.pop();
		if (id == null) return doDelete(path,ex);
		Integer version = null;
		if (VERSION.equals(path.pop())) version = Integer.parseInt(path.pop());
		if (version == null) throw missingField(VERSION);
		var page = loadMembers(wikiDb.load(id,version));
		var member = page.members().get(user.id());
		if (member == null || member.permission() != EDIT) throw forbidden("You are not allowed to delete \"{id}\"!", ID,page.title());
		return sendContent(ex,wikiDb.drop(page));
	}

	@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 PAGE -> deletePage(path, user.get(), ex);
				case null, default -> super.doDelete(path, ex);
			};
		} 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);
			var head = path.pop();
			if (user.isEmpty()) {
				return switch (head){
					case PAGE -> getPage(path, null, ex);
					case null, default -> unauthorized(ex);
				};
			}
			return switch (head) {
				case null -> getUserPages(user.get(),ex);
				case AVAILABLE -> getAvailability(path,ex);
				case PAGE -> getPage(path, user.get(), ex);
				default -> {
					try {
						Long.parseLong(head);
						path.push(head);
						yield getPage(path,user.get(),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 -> getUserPages(user.get(),ex);
				case PAGE -> patchPage(path, user.get(), ex);
				default -> super.doPatch(path,ex);
			};
		} 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();
            if (!path.empty()) return super.doPost(path,ex);
			if (SEARCH.equals(head)) return postSearch(user.get(),ex);
            return postNewPage(head,user.get(),ex);
        } catch (UmbrellaException e){
            return send(ex,e);
        }
    }

	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 pages = wikiDb.find(user.id(),keys,fulltext);
		return sendContent(ex,mapValues(pages));
	}

	private boolean getAvailability(Path path, HttpExchange ex) throws IOException {
        return sendContent(ex,wikiDb.isAvailable(path.pop()));
    }

	private boolean getPage(Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
		var id = path.pop();
		Integer version = null;
		if (!path.empty()){
			var head = path.pop();
			if (!VERSION.equals(head)) return doGet(path,ex);
			try {
				version = Integer.parseInt(path.pop());
			} catch (NumberFormatException e) {
				throw invalidField(VERSION,"int number");
			}
		}
		var page = loadPage(id,version);
		if (page.guestAllowed()) return sendContent(ex,page);
		var permission = page.members().get(user.id());
		if (permission == null) throw forbidden("You are not allowed to access \"{id}\"!", ID,id);
		return sendContent(ex, page);
	}

	private boolean getUserPages(UmbrellaUser user, HttpExchange ex) throws IOException {
		var pageList = wikiDb.listUserPages(user.id());
		return sendContent(ex,pageList);
	}

	private WikiPage loadMembers(WikiPage page) {
		var members = wikiDb.loadMembers(page);
		var users = userService().list(null,null,members.keySet());
		var pageMembers = page.members();
		for (var entry : members.entrySet()){
			var userId = entry.getKey();
			var permission = entry.getValue();
			if (userId == 0){
				if (permission == READ_ONLY) page.guestAllowed(true);
				continue;
			}
			var user = users.get(userId);
			pageMembers.put(userId,new Member(user,permission));
		}
		return page;
	}

	private WikiPage loadPage(String id, Integer version){
		if (id == null) throw missingField(PAGE_ID);
		return loadMembers(wikiDb.load(id, version));
	}

	private boolean patchPage(Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
		var id = path.pop();
		var page = loadPage(id, null);
		var old = page.toMap();
		var member = page.members().get(user.id());
		if (member == null || member.permission() != EDIT) throw forbidden("You are not allowed to edit {0}!", ID,id);
		var json = json(ex);
		page = wikiDb.save(page.patch(json, userService()));
		messageBus().dispatch(new WikiEvent(user,page,old));
		return sendContent(ex,page);
	}

    private boolean postNewPage(String title, UmbrellaUser user, HttpExchange ex) throws IOException {
        var content = body(ex);
        var page = new WikiPage(wikiDb.getNextId(),title,1,content);
        page.members().put(user.id(),new Member(user,EDIT));
        return sendContent(ex,wikiDb.save(page.setNew()));
    }
}
