package ssl

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net"
	"time"

	"arcline-audit/internal/types"
)

// insecureCipherSuites is a set of cipher suite IDs considered weak.
var insecureCipherSuites = map[uint16]bool{
	tls.TLS_RSA_WITH_RC4_128_SHA:                true,
	tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA:           true,
	tls.TLS_RSA_WITH_AES_128_CBC_SHA:            true,
	tls.TLS_RSA_WITH_AES_256_CBC_SHA:            true,
	tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:        true,
	tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA:          true,
	tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:     true,
	tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:    true,
	tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:      true,
	tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:    true,
	tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:      true,
	tls.TLS_RSA_WITH_AES_128_GCM_SHA256:         true,
	tls.TLS_RSA_WITH_AES_256_GCM_SHA384:         true,
}

// Run performs all SSL/TLS checks for the given domain.
func Run(domain string) types.SSLResult {
	addr := net.JoinHostPort(domain, "443")
	dialer := &net.Dialer{Timeout: 10 * time.Second}
	conn, err := tls.DialWithDialer(dialer, "tcp", addr, &tls.Config{
		InsecureSkipVerify: true,
	})
	if err != nil {
		return types.SSLResult{
			Checks: []types.CheckResult{
				{Status: types.StatusFail, Message: fmt.Sprintf("failed to connect: %v", err)},
			},
		}
	}
	defer conn.Close()

	state := conn.ConnectionState()
	var checks []types.CheckResult

	// Certificate validity and details
	checks = append(checks, checkCertificates(state.PeerCertificates)...)

	// TLS version
	checks = append(checks, checkTLSVersion(state.Version)...)

	// Cipher suite
	checks = append(checks, checkCipherSuite(state.CipherSuite)...)

	result := types.SSLResult{Checks: checks}
	if len(state.PeerCertificates) > 0 {
		cert := state.PeerCertificates[0]
		result.Issuer = cert.Issuer.String()
		result.Expiry = cert.NotAfter
		result.DaysLeft = int(time.Until(cert.NotAfter).Hours() / 24)
	}
	result.TLSVersion = tlsVersionName(state.Version)
	return result
}

func checkCertificates(certs []*x509.Certificate) []types.CheckResult {
	var checks []types.CheckResult

	if len(certs) == 0 {
		checks = append(checks, types.CheckResult{
			Status: types.StatusFail, Message: "no certificates presented",
		})
		return checks
	}

	leaf := certs[0]
	now := time.Now()

	// Check expiry
	if now.After(leaf.NotAfter) {
		checks = append(checks, types.CheckResult{
			Status:  types.StatusFail,
			Message: fmt.Sprintf("certificate expired on %s", leaf.NotAfter.Format("2006-01-02")),
		})
	} else {
		daysLeft := int(time.Until(leaf.NotAfter).Hours() / 24)
		if daysLeft < 30 {
			checks = append(checks, types.CheckResult{
				Status:  types.StatusWarn,
				Message: fmt.Sprintf("certificate expires in %d days (%s)", daysLeft, leaf.NotAfter.Format("2006-01-02")),
			})
		} else {
			checks = append(checks, types.CheckResult{
				Status:  types.StatusOK,
				Message: fmt.Sprintf("valid certificate (%s)", leaf.NotAfter.Format("2006-01-02")),
			})
		}
	}

	// Check self-signed
	if leaf.Issuer.String() == leaf.Subject.String() {
		checks = append(checks, types.CheckResult{
			Status: types.StatusWarn, Message: "self-signed certificate",
		})
	} else {
		checks = append(checks, types.CheckResult{
			Status: types.StatusOK, Message: "not self-signed",
		})
	}

	// Check chain completeness
	if len(certs) >= 2 {
		checks = append(checks, types.CheckResult{
			Status: types.StatusOK, Message: "certificate chain is complete",
		})
	} else {
		checks = append(checks, types.CheckResult{
			Status: types.StatusWarn, Message: "certificate chain may be incomplete",
		})
	}

	return checks
}

func checkTLSVersion(vers uint16) []types.CheckResult {
	name := tlsVersionName(vers)
	switch vers {
	case tls.VersionTLS13:
		return []types.CheckResult{{Status: types.StatusOK, Message: fmt.Sprintf("TLS %s", name)}}
	case tls.VersionTLS12:
		return []types.CheckResult{{Status: types.StatusOK, Message: fmt.Sprintf("TLS %s", name)}}
	case tls.VersionTLS11, tls.VersionTLS10:
		return []types.CheckResult{{Status: types.StatusWarn, Message: fmt.Sprintf("TLS %s is insecure", name)}}
	case 0:
		return []types.CheckResult{{Status: types.StatusFail, Message: "unknown TLS version"}}
	default:
		return []types.CheckResult{{Status: types.StatusInfo, Message: fmt.Sprintf("TLS %s", name)}}
	}
}

func checkCipherSuite(id uint16) []types.CheckResult {
	name := tls.CipherSuiteName(id)
	if name == "" {
		name = fmt.Sprintf("unknown (0x%04X)", id)
	}
	if insecureCipherSuites[id] {
		return []types.CheckResult{{Status: types.StatusWarn, Message: fmt.Sprintf("weak cipher suite: %s", name)}}
	}
	return []types.CheckResult{{Status: types.StatusOK, Message: fmt.Sprintf("cipher suite: %s", name)}}
}

func tlsVersionName(v uint16) string {
	switch v {
	case tls.VersionTLS10:
		return "1.0"
	case tls.VersionTLS11:
		return "1.1"
	case tls.VersionTLS12:
		return "1.2"
	case tls.VersionTLS13:
		return "1.3"
	default:
		return fmt.Sprintf("0x%04X", v)
	}
}

