package nodeprocessor

import (
	"context"
	"crypto/tls"
	"fmt"
	"math"
	"net"
	"time"

	"github.com/usual2970/certimate/internal/domain"
)

type inspectNode struct {
	node *domain.WorkflowNode
	*nodeProcessor
	*nodeOutputer
}

func NewInspectNode(node *domain.WorkflowNode) *inspectNode {
	return &inspectNode{
		node:          node,
		nodeProcessor: newNodeProcessor(node),
		nodeOutputer:  newNodeOutputer(),
	}
}

func (n *inspectNode) Process(ctx context.Context) error {
	n.logger.Info("enter inspect website certificate node ...")

	nodeConfig := n.node.GetConfigForInspect()

	err := n.inspect(ctx, nodeConfig)
	if err != nil {
		n.logger.Warn("inspect website certificate failed: " + err.Error())
		return err
	}

	return nil
}

func (n *inspectNode) inspect(ctx context.Context, nodeConfig domain.WorkflowNodeConfigForInspect) error {
	// 定义重试参数
	maxRetries := 3
	retryInterval := 2 * time.Second

	var cert *tls.Certificate
	var lastError error

	domainWithPort := nodeConfig.Domain + ":" + nodeConfig.Port

	for attempt := 0; attempt < maxRetries; attempt++ {
		if attempt > 0 {
			n.logger.Info(fmt.Sprintf("Retry #%d connecting to %s", attempt, domainWithPort))
			select {
			case <-ctx.Done():
				return ctx.Err()
			case <-time.After(retryInterval):
				// Wait for retry interval
			}
		}

		dialer := &net.Dialer{
			Timeout: 10 * time.Second,
		}

		conn, err := tls.DialWithDialer(dialer, "tcp", domainWithPort, &tls.Config{
			InsecureSkipVerify: true, // Allow self-signed certificates
		})
		if err != nil {
			lastError = fmt.Errorf("failed to connect to %s: %w", domainWithPort, err)
			n.logger.Warn(fmt.Sprintf("Connection attempt #%d failed: %s", attempt+1, lastError.Error()))
			continue
		}

		// Get certificate information
		certInfo := conn.ConnectionState().PeerCertificates[0]
		conn.Close()

		// Certificate information retrieved successfully
		cert = &tls.Certificate{
			Certificate: [][]byte{certInfo.Raw},
			Leaf:        certInfo,
		}
		lastError = nil
		n.logger.Info(fmt.Sprintf("Successfully retrieved certificate information for %s", domainWithPort))
		break
	}

	if lastError != nil {
		return fmt.Errorf("failed to retrieve certificate after %d attempts: %w", maxRetries, lastError)
	}

	certInfo := cert.Leaf
	now := time.Now()

	isValid := now.Before(certInfo.NotAfter) && now.After(certInfo.NotBefore)

	// Check domain matching
	domainMatch := false
	if len(certInfo.DNSNames) > 0 {
		for _, dnsName := range certInfo.DNSNames {
			if matchDomain(nodeConfig.Domain, dnsName) {
				domainMatch = true
				break
			}
		}
	} else if matchDomain(nodeConfig.Domain, certInfo.Subject.CommonName) {
		domainMatch = true
	}

	isValid = isValid && domainMatch

	daysRemaining := math.Floor(certInfo.NotAfter.Sub(now).Hours() / 24)

	// Set node outputs
	outputs := map[string]any{
		"certificate.validated": isValid,
		"certificate.daysLeft":  daysRemaining,
	}
	n.setOutputs(outputs)

	return nil
}

func (n *inspectNode) setOutputs(outputs map[string]any) {
	n.outputs = outputs
}

func matchDomain(requestDomain, certDomain string) bool {
	if requestDomain == certDomain {
		return true
	}

	if len(certDomain) > 2 && certDomain[0] == '*' && certDomain[1] == '.' {

		wildcardSuffix := certDomain[1:]
		requestDomainLen := len(requestDomain)
		suffixLen := len(wildcardSuffix)

		if requestDomainLen > suffixLen && requestDomain[requestDomainLen-suffixLen:] == wildcardSuffix {
			remainingPart := requestDomain[:requestDomainLen-suffixLen]
			if len(remainingPart) > 0 && !contains(remainingPart, '.') {
				return true
			}
		}
	}

	return false
}

func contains(s string, c byte) bool {
	for i := 0; i < len(s); i++ {
		if s[i] == c {
			return true
		}
	}
	return false
}