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

import static de.srsoftware.tools.MimeType.MIME_HTML;
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
import static de.srsoftware.umbrella.core.constants.Field.BASE_URL;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingField;
import static java.lang.System.Logger.Level.*;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.sun.net.httpserver.HttpExchange;
import com.xrbpowered.jparsedown.JParsedown;
import de.srsoftware.configuration.Configuration;
import de.srsoftware.tools.Path;
import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.ModuleRegistry;
import de.srsoftware.umbrella.core.api.MarkdownService;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;

public class MarkdownApi extends BaseHandler implements MarkdownService {

	private static final System.Logger LOG           = System.getLogger(MarkdownApi.class.getSimpleName());
	private static final Pattern PATTERN_ANCHOR      = Pattern.compile("(?is)<a\\b[^>]*>.*?</a>");
	private static final Pattern PATTERN_HREF        = Pattern.compile("(?i)\\bhref\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))");
	private static final Pattern PATTERN_SPREADSHEET = Pattern.compile("@startsheet(.*?)@endsheet",Pattern.DOTALL);
	private static final Pattern PATTERN_TARGET      = Pattern.compile("(?i)\\btarget\\s*=");
	private static final Pattern PATTERN_URL         = Pattern.compile("@start(\\w+)(.*?)@end(\\1)",Pattern.DOTALL);
	private static final Map<Integer,String> umlCache = new HashMap<>();
	private static final JParsedown MARKDOWN = new JParsedown();


	private final String baseUrl;
	private final File plantumlJar;

	public MarkdownApi(Configuration config) {
		super();
		Optional<String> baseUrl = config.get(BASE_URL);
		if (baseUrl.isEmpty()) throw missingField(BASE_URL);
		this.baseUrl = baseUrl.get();
		plantumlJar = config.get("umbrella.plantuml").map(Object::toString).map(File::new).filter(File::exists).orElse(null);
		if (plantumlJar != null) LOG.log(INFO,"Using plant uml @ {}",plantumlJar);
		ModuleRegistry.add(this);
	}

	@Override
	public boolean doPost(Path path, HttpExchange ex) throws IOException {
		try {
			addCors(ex);
			var user = userService().refreshSession(ex);

			if (user.isEmpty()) throw UmbrellaException.forbidden("You must be logged in to use the markdown renderer!");
			var rendered = markdown(body(ex));

			ex.getResponseHeaders().add(CONTENT_TYPE,MIME_HTML);
			return sendContent(ex,rendered);
		} catch (UmbrellaException e){
			return send(ex,e);
		}
	}

	public String markdown(String source){
		if (source == null || source.isBlank()) return source;
		try {
			var matcher = PATTERN_SPREADSHEET.matcher(source);
			var count = 0;
			while (matcher.find()){
				LOG.log(DEBUG,"Processing spreadsheet code…");
				count++;
				var sheetData = matcher.group(0).trim();
				var start = matcher.start(0);
				var end = matcher.end(0);
				source = source.substring(0, start)
						+ "<div class=\"spreadsheet\" id=\"spreadsheet-"+count+"\">"
						+ sheetData.substring(11,sheetData.length()-10)
						+ "</div>"
						+ source.substring(end);
				LOG.log(DEBUG,"Updated markdown with spreadsheet div.");
				matcher.reset(source);
			}
			if (plantumlJar != null && plantumlJar.exists()) {
				matcher = PATTERN_URL.matcher(source);
				while (matcher.find()) {
					var uml = matcher.group(0).trim();
					var start = matcher.start(0);
					var end = matcher.end(0);

					var umlHash = uml.hashCode();
					LOG.log(DEBUG,"Hash of Plantuml code: {0}",umlHash);
					var svg = umlCache.get(umlHash);
					if (svg != null){
						LOG.log(DEBUG,"Serving Plantuml generated SVG from cache…");
						source = source.substring(0, start) + svg + source.substring(end);
						matcher.reset(source);
						continue;
					}

					LOG.log(DEBUG,"Cache miss. Generating SVG from plantuml code…");
					ProcessBuilder processBuilder = new ProcessBuilder("java", "-jar", plantumlJar.getAbsolutePath(), "-tsvg", "-pipe");
					var ignored = processBuilder.redirectErrorStream();
					var process = processBuilder.start();
					try (OutputStream os = process.getOutputStream()) {
						os.write(uml.getBytes(UTF_8));
						os.flush();
					}

					try (InputStream is = process.getInputStream()) {
						byte[] out = is.readAllBytes();
						LOG.log(DEBUG,"Generated SVG. Pushing to cache…");
						svg = new String(out, UTF_8);
						umlCache.put(umlHash,svg);
						source = source.substring(0, start) + svg + source.substring(end);
						matcher.reset(source);
					}
				}
			}
			var rendered = MARKDOWN.text(source);
			if (baseUrl == null) return rendered;
			var anchors = PATTERN_ANCHOR.matcher(rendered);
			while (anchors.find()){
				String anchor = anchors.group();
				LOG.log(TRACE,"Processing anchor: {}",anchor);
				var urls = PATTERN_HREF.matcher(anchor);
				if (!urls.find()) continue; // no url → nothing to do

				var href = urls.group(1) != null ? urls.group(1) : (urls.group(2) != null ? urls.group(2) : urls.group(3));
				LOG.log(TRACE,"  encountered href = {}",href);
				if (!href.startsWith("http")) continue; // relative url? good!
				LOG.log(TRACE,"  {} is not a relative url!",href);
				var target = PATTERN_TARGET.matcher(anchor);
				if (target.find()) continue; // target already set → leave untouched
				LOG.log(TRACE,"  anchor has no target!");
				if (href.startsWith(baseUrl)) continue; // local url → don`t touch
				LOG.log(TRACE,"  {} is not an internal url, adding anchor…",href);
				var replacement = "<a target=\"_blank\""+anchor.substring(2);
				rendered = rendered.replace(anchor, replacement);
				anchors.reset(rendered);
			}
			return rendered;
		} catch (Throwable e){
			if (LOG.isLoggable(TRACE)){
				LOG.log(TRACE,"Failed to render markdown, input was: \n{0}",source,e);
			} else LOG.log(WARNING,"Failed to render markdown. Enable TRACE log level for details.");
			return source;
		}
	}
}
