package com.aip.gateway.api;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

@Slf4j
public class LicenseManager {

    @Data
    private static class License {
        private boolean isValid;
        private int errorCode;
        private String product;
        private String serialId;
        private String issueDay;
        private Date startDate;
        private String licensee;
        private String edition;
        private String type;
        private int accounts;
        private String identifiedHost;
        private String signature;
        private int demoDuration;
        private boolean isDemo;

        public License() {
            this.isValid = false;
            this.errorCode = 0;
            this.product = "";
            this.serialId = "";
            this.issueDay = "";
            this.licensee = "";
            this.edition = "";
            this.type = "";
            this.accounts = 0;
            this.identifiedHost = "";
            this.signature = "";
            this.demoDuration = 0;
            this.isDemo = false;
        }
    }

    private static String getComputerName() {
        String hostName = System.getenv("COMPUTERNAME");
        if (hostName == null || hostName.isEmpty()) {
            hostName = "localhost";
        }
        return hostName;
    }

    private static NodeList getXmlNodeList(String filePath, String nodeTag) {
        try {
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

            File xmlFile = new File(filePath);

            Document document = documentBuilder.parse(xmlFile);

            document.getDocumentElement().normalize();
            return document.getElementsByTagName(nodeTag);
        } catch (ParserConfigurationException e) {
            log.error("getXmlNodeList(ParserConfigurationException): {}, {}: {}", filePath, nodeTag, e.toString());
        } catch (SAXException e) {
            log.error("getXmlNodeList(SAXException): {}, {}: {}", filePath, nodeTag, e.toString());
        } catch (IOException e) {
            log.error("getXmlNodeList(IOException): {}, {}: {}", filePath, nodeTag, e.toString());
        } catch (Exception e) {
            log.error("getXmlNodeList(Exception): {}, {}: {}", filePath, nodeTag, e.toString());
        }
        return null;
    }

    private static String getElementTagValue(Element element, String tagName) {
        NodeList list = element.getElementsByTagName(tagName);
        if (list == null) {
            return null;
        }
        Node node = list.item(0);
        if (node == null) {
            return null;
        }
        return node.getTextContent().trim();
    }

    private static License errorLicenseItem(License license, int errorCode) {
        license.setErrorCode(errorCode);
        if (errorCode > 20) {
            log.error("License item data validation error: {}", errorCode);
        }
        else {
            log.error("License item data not found: {}", errorCode);
        }
        return license;
    }

    private static boolean errorLicenseItemValue(String orgValue, String curValue, int errorCode) {
        //log.error("License value error: {}, {}, {}", orgValue, curValue, errorCode);
        log.error("License value error: {}, {}", curValue, errorCode);
        return false;
    }
    private static Date getDay(String dayString) {
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Date date = dateFormat.parse(dayString);
            return date;
        } catch (ParseException e) {
            return null;
        }
    }
    private static boolean isValidIntegerString(String inputString) {
        try {
            Integer.parseInt(inputString);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }
    private static License loadLicenseFile() {
        License license = new License();
        String filePath = System.getProperty("user.dir") + File.separator + "license" + File.separator + "license.xml";
        //log.info("Loading license: {}", filePath);
        NodeList nodeList = getXmlNodeList(filePath, "license");
        if (nodeList == null) {
            return license;
        }
        for (int ii = 0; ii < nodeList.getLength(); ii++) {
            Node node = nodeList.item(ii);
            if (node.getNodeType() != 1)
                continue;

            Element element = (Element) node;

//            log.info("       product: {}", getElementTagValue(element, "product"));
//            log.info("      serialId: {}", getElementTagValue(element, "serialId"));
//            log.info("      issueDay: {}", getElementTagValue(element, "issueDay"));
//            log.info("       edition: {}", getElementTagValue(element, "edition"));
//            log.info("          type: {}", getElementTagValue(element, "type"));
//            log.info("      accounts: {}", getElementTagValue(element, "accounts"));
//            log.info("identifiedHost: {}", getElementTagValue(element, "identifiedHost"));
//            log.info("     signature: {}", getElementTagValue(element, "signature"));
//            log.info("  demoDuration: {}", getElementTagValue(element, "demoDuration"));

            String product = getElementTagValue(element, "product");
            if (product == null) {
                return errorLicenseItem(license, 1);
            }
            license.setProduct(product);

            String serialId = getElementTagValue(element, "serialId");
            if (serialId == null) {
                return errorLicenseItem(license, 2);
            }
            license.setSerialId(serialId);

            String issueDay = getElementTagValue(element, "issueDay");
            if (issueDay == null) {
                return errorLicenseItem(license, 3);
            }
            Date day = getDay(issueDay);
            if (day == null) {
                return errorLicenseItem(license, 33);
            }
            license.setIssueDay(issueDay);
            license.setStartDate(day);

            String edition = getElementTagValue(element, "edition");
            if (edition == null) {
                return errorLicenseItem(license, 4);
            }
            if (!"standard".equalsIgnoreCase(edition) && !"enterprise".equalsIgnoreCase(edition)) {
                return errorLicenseItem(license, 34);
            }
            license.setEdition(edition);

            String type = getElementTagValue(element, "type");
            if (type == null) {
                return errorLicenseItem(license, 5);
            }
            if (!"demo".equalsIgnoreCase(type) && !"product".equalsIgnoreCase(type)) {
                return errorLicenseItem(license, 35);
            }
            license.setType(type);
            if ("demo".equalsIgnoreCase(type)) {
                license.setDemo(true);
            }

            String accounts = getElementTagValue(element, "accounts");
            if (accounts == null) {
                return errorLicenseItem(license, 6);
            }
            if (!isValidIntegerString(accounts)) {
                return errorLicenseItem(license, 36);
            }
            license.setAccounts(Integer.parseInt(accounts));

            String identifiedHost = getElementTagValue(element, "identifiedHost");
            if (identifiedHost == null) {
                return errorLicenseItem(license, 7);
            }
            license.setIdentifiedHost(identifiedHost);

            String signature = getElementTagValue(element, "signature");
            if (signature == null) {
                return errorLicenseItem(license, 8);
            }
            license.setSignature(signature);

            if (license.isDemo()) {
                String demoDuration = getElementTagValue(element, "demoDuration");
                if (demoDuration == null) {
                    return errorLicenseItem(license, 9);
                }
                if (!isValidIntegerString(demoDuration)) {
                    return errorLicenseItem(license, 39);
                }
                license.setDemoDuration(Integer.parseInt(demoDuration));
            }
        }
        license.setValid(true);
        return license;
    }

    public static String getSha256Encrypt(String str) {
        String SHA = "";
        try {
            MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
            //msgDigest.update(str.getBytes());
            msgDigest.update(str.getBytes(StandardCharsets.UTF_8));
            byte byteData[] = msgDigest.digest();
            StringBuffer builder = new StringBuffer();

            for (byte b : byteData) {
                builder.append(String.format("%02x", b));
            }
            SHA = builder.toString();
        } catch (NoSuchAlgorithmException e) {
            SHA = "";
        }
        return SHA;
    }

    public static boolean validateLicense() {

        log.info("Start License validateLicense.");
        License license = loadLicenseFile();
        if (!license.isValid()) {
            return false;
        }

        if (license.isDemo()) {
            // 데모 모드 일 경우 유효기간 체크
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(license.getStartDate());
            calendar.add(Calendar.DAY_OF_MONTH, license.getDemoDuration());
            Date resultDate = calendar.getTime();
            Date currentDate = new Date();
            if (!currentDate.before(resultDate)) {
                log.error("License Issue Day expired.");
                return false;
            }
        }

        String identifiedHost = getComputerName();
        if (!identifiedHost.equalsIgnoreCase(license.getIdentifiedHost())) {
            // 컴퓨터 이름 체크
            return errorLicenseItemValue(identifiedHost, license.getIdentifiedHost(), 1);
        }

        String encKey = license.getProduct().toUpperCase() + "hanteinfo12#$!" +
                license.getSerialId().toLowerCase() +
                license.getIssueDay() +
                license.getEdition().toUpperCase() +
                license.getType().toLowerCase() +
                String.valueOf(license.getAccounts() + 10000) +
                license.getIdentifiedHost().toLowerCase();

        String signature = getSha256Encrypt(encKey);
        if (!signature.equalsIgnoreCase(license.getSignature())) {
            // 컴퓨터 이름 체크
            return errorLicenseItemValue(signature, license.getSignature(), 2);
        }
        log.info("..End License validateLicense.OK.");
        return true;
    }
}
