package com.hotwj.platform.reportStatistics.verify.service;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.hotwj.platform.reportStatistics.verify.config.AppVerifyProperties;
import com.hotwj.platform.reportStatistics.verify.domain.HotReportVerifyTicket;
import com.hotwj.platform.reportStatistics.verify.mapper.HotReportVerifyTicketMapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

@Service
@RequiredArgsConstructor
public class ReportVerifyTicketService {

    private static final String SEAL_RESOURCE = "images/officialInformationSeal.png";
    private static volatile String cachedSealDataUrl;
    private final HotReportVerifyTicketMapper ticketMapper;
    private final AppVerifyProperties appVerifyProperties;

    public Long createTicketId() {
        return IdWorker.getId();
    }

    public Long issueHtmlSnapshotTicket(Long ticketId, String module, Long companyId, String businessParamsJson, String snapshotHtml) {
        if (Boolean.FALSE.equals(appVerifyProperties.getVerify().getEnabled())) {
            throw new ServiceException("验真功能未开启");
        }
        if (Boolean.FALSE.equals(appVerifyProperties.getVerify().getSnapshotMode())) {
            throw new ServiceException("当前仅支持验真快照模式");
        }
        if (StrUtil.isBlank(appVerifyProperties.getVerify().getSecret())) {
            throw new ServiceException("验真签名密钥未配置");
        }
        if (StrUtil.isBlank(module)) {
            throw new ServiceException("验真模块不能为空");
        }
        if (companyId == null) {
            throw new ServiceException("公司ID不能为空");
        }
        if (StrUtil.isBlank(snapshotHtml)) {
            throw new ServiceException("快照内容不能为空");
        }

        Long id = ticketId == null ? IdWorker.getId() : ticketId;
        Date issuedAt = truncateToSeconds(new Date());
        Date expireAt = buildExpireAt(issuedAt);
        String nonce = cn.hutool.core.util.IdUtil.fastSimpleUUID();
        String snapshotHash = SecureUtil.sha256(snapshotHtml);
        String sign = sign(module, companyId, businessParamsJson, snapshotHash, issuedAt, nonce);

        HotReportVerifyTicket entity = new HotReportVerifyTicket();
        entity.setId(id);
        entity.setCompanyId(companyId);
        entity.setModule(module);
        entity.setBusinessParams(businessParamsJson);
        entity.setSnapshotType("HTML");
        entity.setSnapshotContent(snapshotHtml);
        entity.setSnapshotHash(snapshotHash);
        entity.setIssuedAt(issuedAt);
        entity.setExpireAt(expireAt);
        entity.setStatus(1);
        entity.setNonce(nonce);
        entity.setSign(sign);
        entity.setIsDeleted(0L);

        ticketMapper.insert(entity);
        return id;
    }

    public Long issueHtmlSnapshotTicket(String module, Long companyId, String businessParamsJson, String snapshotHtml) {
        return issueHtmlSnapshotTicket(null, module, companyId, businessParamsJson, snapshotHtml);
    }

    public HotReportVerifyTicket loadAndValidate(Long ticketId, String expectedModule) {
        if (ticketId == null) {
            throw new ServiceException("ticket不能为空");
        }
        HotReportVerifyTicket ticket = ticketMapper.selectById(ticketId);
        if (ticket == null || (ticket.getIsDeleted() != null && ticket.getIsDeleted() == 1L)) {
            throw new ServiceException("验真票据不存在");
        }
        if (ticket.getStatus() == null || ticket.getStatus() != 1) {
            throw new ServiceException("验真票据已作废");
        }
        if (StrUtil.isNotBlank(expectedModule) && !StrUtil.equals(expectedModule, ticket.getModule())) {
            throw new ServiceException("验真票据类型不匹配");
        }
        if (ticket.getExpireAt() != null && ticket.getExpireAt().before(new Date())) {
            throw new ServiceException("验真票据已过期");
        }
        if (StrUtil.isBlank(appVerifyProperties.getVerify().getSecret())) {
            throw new ServiceException("验真签名密钥未配置");
        }
        String snapshotHash = SecureUtil.sha256(ticket.getSnapshotContent() == null ? "" : ticket.getSnapshotContent());
        if (!StrUtil.equals(snapshotHash, ticket.getSnapshotHash())) {
            throw new ServiceException("验真快照校验失败");
        }
        String expectedSign = sign(ticket.getModule(), ticket.getCompanyId(), ticket.getBusinessParams(), ticket.getSnapshotHash(), ticket.getIssuedAt(), ticket.getNonce());
        if (!StrUtil.equals(expectedSign, ticket.getSign())) {
            throw new ServiceException("验真签名校验失败");
        }
        return ticket;
    }

    public String buildVerifyUrl(String path, Long ticketId) {
        String baseUrl = appVerifyProperties.getWebBaseUrl();
        if (StrUtil.isBlank(baseUrl)) {
            throw new ServiceException("app.web-base-url 未配置");
        }
        String normalizedBase = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
        String normalizedPath = path.startsWith("/") ? path : ("/" + path);
        return normalizedBase + normalizedPath + "?ticket=" + ticketId;
    }

    public String normalizeSnapshotHtmlForMobile(String html) {
        if (StrUtil.isBlank(html)) {
            return html;
        }
        String out = ensureViewport(html);
        return injectSealTopRight(out);
    }

    private String ensureViewport(String html) {
        if (html.contains("name=\"viewport\"")) {
            return html;
        }
        int headIdx = html.indexOf("<head>");
        if (headIdx >= 0) {
            int insertPos = headIdx + "<head>".length();
            return html.substring(0, insertPos)
                    + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
                    + html.substring(insertPos);
        }
        return html;
    }

    private String injectSealTopRight(String html) {
        if (StrUtil.isBlank(html)) {
            return html;
        }
        if (html.contains("id=\"verify-seal-img\"")) {
            return html;
        }
        String sealDataUrl = getSealDataUrl();
        if (StrUtil.isBlank(sealDataUrl)) {
            return html;
        }

        String style = "<style id=\"verify-seal-style\">"
                + ".verify-seal{position:absolute;top:16px;right:16px;width:120px;opacity:.95;z-index:9999;pointer-events:none;}"
                + "@media(max-width:480px){.verify-seal{top:10px;right:10px;width:90px;}}"
                + "</style>";

        String img = "<img id=\"verify-seal-img\" class=\"verify-seal\" src=\"" + sealDataUrl + "\" alt=\"seal\"/>";

        String out = html;
        if (!out.contains("id=\"verify-seal-style\"")) {
            int headIdx = out.indexOf("</head>");
            if (headIdx >= 0) {
                out = out.substring(0, headIdx) + style + out.substring(headIdx);
            } else {
                int headOpen = out.indexOf("<head>");
                if (headOpen >= 0) {
                    int insertPos = headOpen + "<head>".length();
                    out = out.substring(0, insertPos) + style + out.substring(insertPos);
                }
            }
        }

        int bodyIdx = out.indexOf("<body");
        if (bodyIdx >= 0) {
            int bodyEnd = out.indexOf(">", bodyIdx);
            if (bodyEnd >= 0) {
                int insertPos = bodyEnd + 1;
                out = out.substring(0, insertPos) + img + out.substring(insertPos);
                return out;
            }
        }

        return "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
                + style
                + "</head><body>"
                + img
                + out
                + "</body></html>";
    }

    private String getSealDataUrl() {
        if (cachedSealDataUrl != null) {
            return cachedSealDataUrl;
        }
        synchronized (ReportVerifyTicketService.class) {
            if (cachedSealDataUrl != null) {
                return cachedSealDataUrl;
            }
            try {
                ClassPathResource resource = new ClassPathResource(SEAL_RESOURCE);
                if (!resource.exists()) {
                    return null;
                }
                try (InputStream is = resource.getInputStream()) {
                    byte[] bytes = is.readAllBytes();
                    cachedSealDataUrl = "data:image/png;base64," + Base64.encode(bytes);
                    return cachedSealDataUrl;
                }
            } catch (Exception e) {
                return null;
            }
        }
    }

    private Date buildExpireAt(Date issuedAt) {
        Integer days = appVerifyProperties.getVerify().getTicketExpireDays();
        if (days == null || days <= 0) {
            return null;
        }
        LocalDateTime expire = LocalDateTime.ofInstant(issuedAt.toInstant(), ZoneId.systemDefault()).plusDays(days);
        return Date.from(expire.atZone(ZoneId.systemDefault()).toInstant());
    }

    private String sign(String module, Long companyId, String businessParamsJson, String snapshotHash, Date issuedAt, String nonce) {
        String secret = appVerifyProperties.getVerify().getSecret();
        long issuedSeconds = issuedAt == null ? 0L : (issuedAt.getTime() / 1000L); // 以秒对齐，避免DATETIME毫秒精度丢失导致验签失败
        String payload = StrUtil.join("|",
                StrUtil.blankToDefault(module, ""),
                companyId == null ? "" : String.valueOf(companyId),
                StrUtil.blankToDefault(businessParamsJson, ""),
                StrUtil.blankToDefault(snapshotHash, ""),
                String.valueOf(issuedSeconds),
                StrUtil.blankToDefault(nonce, "")
        );
        return SecureUtil.hmacSha256(secret.getBytes(StandardCharsets.UTF_8)).digestHex(payload);
    }

    private Date truncateToSeconds(Date d) {
        if (d == null) return null;
        long sec = (d.getTime() / 1000L) * 1000L;
        return new Date(sec);
    }
}
