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

import static de.srsoftware.tools.MimeType.MIME_FORM_URL;
import static de.srsoftware.tools.MimeType.MIME_JSON;
import static de.srsoftware.tools.PathHandler.GET;
import static de.srsoftware.tools.PathHandler.POST;
import static de.srsoftware.tools.Strings.hex;
import static de.srsoftware.umbrella.core.Errors.INVALID_URL;
import static de.srsoftware.umbrella.core.ModuleRegistry.markdownService;
import static de.srsoftware.umbrella.core.constants.Constants.TIME_FORMATTER;
import static de.srsoftware.umbrella.core.constants.Field.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.serverError;
import static java.lang.System.Logger.Level.*;
import static java.lang.System.Logger.Level.WARNING;
import static java.nio.charset.StandardCharsets.UTF_8;

import de.srsoftware.tools.Mappable;
import de.srsoftware.tools.Query;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Hash;
import de.srsoftware.umbrella.core.model.Translatable;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONObject;

public class Util {
	public  static final System.Logger LOG            = System.getLogger("Util");
	public  static final String SHA1 = "SHA-1";
	private static final MessageDigest SHA1_DIGEST;

	private static final String SCRIPT = """
<script src="http://127.0.0.1:8080/js/jspreadsheet-ce.js"></script>
<div id="spreadsheet"></div>
<script type="application/javascript">
alert('Test');
jspreadsheet(document.getElementById('spreadsheet'), {
    worksheets: [
        {
            data: [
                ['Jazz', 'Honda', '2019-02-12', '', true, '$ 2.000,00', '#777700'],
                ['Civic', 'Honda', '2018-07-11', '', true, '$ 4.000,01', '#007777'],
            ],
            columns: [
                { type: 'text', title: 'Car', width: 120 },
                {
                    type: 'dropdown',
                    title: 'Make',
                    width: 200,
                    source: ['Alfa Romeo', 'Audi', 'Bmw', 'Honda'],
                },
                { type: 'calendar', title: 'Available', width: 200 },
                { type: 'image', title: 'Photo', width: 120 },
                { type: 'checkbox', title: 'Stock', width: 80 },
                {
                    type: 'numeric',
                    title: 'Price',
                    width: 100,
                    mask: '$ #.##,00',
                    decimal: ',',
                },
                { type: 'color', width: 100, render: 'square' },
            ],
        },
    ],
});
</script>
			""";

	static {
		try {
			SHA1_DIGEST = MessageDigest.getInstance(SHA1);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
	}

	private Util(){}

	public static System.Logger.Level mapLogLevel(String lbl) {
		return switch (lbl){
			case "ERROR" -> ERROR;
			case "WARN" -> WARNING;
			case "DEBUG" -> DEBUG;
			case "TRACE" -> TRACE;
			default -> INFO;
		};
	}

	public static <KEY> Map<KEY,Map<String,Object>> mapValues(Map<KEY, ? extends Mappable> map){
		var result = new HashMap<KEY,Map<String,Object>>();
		for (var entry : map.entrySet()) result.put(entry.getKey(),entry.getValue().toMap());
		return result;
	}

	public static HashMap<String, Object> mapMarkdown(String source){
		var map = new HashMap<String,Object>();
		map.put(SOURCE,source);
		map.put(RENDERED,markdownService().markdown(source));
		return map;
	}



	public static HttpURLConnection open(URL url) throws IOException {
		var conn = (HttpURLConnection) url.openConnection();
		conn.setRequestProperty("Accept","*/*");
		conn.setRequestProperty("Host",url.getHost());
		conn.setRequestProperty("User-Agent","Umbrella/0.1");
		return conn;
	}

	public static Object request(String location, Map<String,?> data, String postMime, String auth) throws UmbrellaException {
		URL url;
		try {
			url = new URI(location).toURL();
		} catch (Exception e) {
			LOG.log(WARNING,"{0} is not a valid url",location,e);
			throw serverError(INVALID_URL,URL,location).causedBy(e);
		}
		return request(url,data,postMime,auth);
	}

	public static Object request(URL target, Map<String,?> data, String postMime, String auth) throws UmbrellaException {
		String query = null;
		if (data != null) {
			query = switch (postMime){
				case MIME_FORM_URL -> Query.encode(data).orElse(null);
				case null, default -> {
					postMime = MIME_JSON;
					yield new JSONObject(data).toString();
				}
			};
		}
		var method = query == null ? GET : POST;
		try {
			LOG.log(DEBUG, "sending {0} request ({1}) to {2}", method, postMime == null ? "empty" : postMime, target);
			LOG.log(TRACE, "postData = {0}", query);
			var conn = open(target);
			conn.setRequestMethod(method);
			conn.setRequestProperty(CONTENT_TYPE, postMime);
			if (auth != null) conn.setRequestProperty(AUTHORIZATION, auth);
			if (query != null) {
				conn.setDoOutput(true);
				var out = conn.getOutputStream();
				out.write(query.getBytes(UTF_8));
				out.flush();
				out.close();
			}
			var bos = new ByteArrayOutputStream();
			if (conn.getResponseCode() == 200) {
				var is = conn.getInputStream();
				is.transferTo(bos);
				is.close();
				var content = bos.toString(UTF_8);
				if (content.startsWith("{")) return new JSONObject(content);
				return content;
			} else {
				var is = conn.getErrorStream();
				is.transferTo(bos);
				is.close();
				throw new UmbrellaException(500, Translatable.t(bos.toString(UTF_8)));
			}
		} catch (UmbrellaException e){
			throw e;
		} catch (Exception e) {
			LOG.log(WARNING,"Request to {0} failed: {1}",target,e.getMessage());
			throw new UmbrellaException(500, Translatable.t("Request to {url} failed!",URL,target)).causedBy(e);
		}
	}

	public static Hash sha1(String plain){
		var bytes = SHA1_DIGEST.digest(plain.getBytes(UTF_8));
		return new Hash(hex(bytes),SHA1);
	}

	public static String dateTimeOf(long epochMilis){
		return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilis), ZoneId.systemDefault()).format(TIME_FORMATTER);
	}
}
