package cert

import (
	"crypto/rsa"
	"crypto/x509"
	"math"
	"os"
	"time"

	goPkcs12 "software.sslmate.com/src/go-pkcs12"
	stdPkcs12 "golang.org/x/crypto/pkcs12"
)

type ValidationResult struct {
	Valid    bool      `json:"valid"`
	CertInfo *CertInfo `json:"cert_info,omitempty"`
	Warnings []string  `json:"warnings,omitempty"`
	Error    string    `json:"error,omitempty"`
}

type CertInfo struct {
	Subject         string `json:"subject"`
	Issuer          string `json:"issuer"`
	NotBefore       string `json:"not_before"`
	NotAfter        string `json:"not_after"`
	Expired         bool   `json:"expired"`
	ExpiringSoon    bool   `json:"expiring_soon"`
	DaysUntilExpiry int    `json:"days_until_expiry"`
}

const WarningDaysThreshold = 30

func ValidateP12(filePath, password string) *ValidationResult {
	p12Data, err := os.ReadFile(filePath)
	if err != nil {
		result := &ValidationResult{Valid: true}
		if os.IsNotExist(err) {
			result.Valid = false
			result.Error = "file_not_found"
			return result
		}
		result.Valid = false
		result.Error = "invalid_password_or_format"
		return result
	}

	return ValidateP12Bytes(p12Data, password)
}

// ValidateP12Bytes valida un P12 a partir de sus bytes (util para tests sin fichero).
// Soporta tanto P12 simples (go-pkcs12) como P12 con cadena de certificados (x/crypto/pkcs12).
func ValidateP12Bytes(p12Data []byte, password string) *ValidationResult {
	result := &ValidationResult{Valid: true}

	x509Cert, err := decodeLeafCert(p12Data, password)
	if err != nil {
		result.Valid = false
		result.Error = "invalid_password_or_format"
		return result
	}

	now := time.Now()

	if now.Before(x509Cert.NotBefore) {
		result.Valid = false
		result.Error = "certificate_not_yet_valid"
		return result
	}

	if now.After(x509Cert.NotAfter) {
		result.Valid = false
		result.Error = "certificate_expired"
		result.CertInfo = &CertInfo{
			Subject:   x509Cert.Subject.CommonName,
			Issuer:    x509Cert.Issuer.CommonName,
			NotBefore: x509Cert.NotBefore.Format("2006-01-02"),
			NotAfter:  x509Cert.NotAfter.Format("2006-01-02"),
			Expired:   true,
		}
		return result
	}

	daysUntilExpiry := int(math.Ceil(x509Cert.NotAfter.Sub(now).Hours() / 24))

	result.CertInfo = &CertInfo{
		Subject:         x509Cert.Subject.CommonName,
		Issuer:          x509Cert.Issuer.CommonName,
		NotBefore:       x509Cert.NotBefore.Format("2006-01-02"),
		NotAfter:        x509Cert.NotAfter.Format("2006-01-02"),
		DaysUntilExpiry: daysUntilExpiry,
	}

	if daysUntilExpiry <= WarningDaysThreshold {
		result.Warnings = append(result.Warnings, "certificate_expiring_soon")
		result.CertInfo.ExpiringSoon = true
	}

	return result
}

// decodeLeafCert extrae el certificado hoja del P12.
// Intenta primero go-pkcs12 (P12 simples) y luego x/crypto/pkcs12 (P12 con cadena FNMT/ACCV).
func decodeLeafCert(p12Data []byte, password string) (*x509.Certificate, error) {
	// Intento 1: go-pkcs12 — soporta P12 simples (1 clave + 1 cert)
	_, cert, err := goPkcs12.Decode(p12Data, password)
	if err == nil {
		return cert, nil
	}

	// Intento 2: x/crypto/pkcs12 — soporta P12 con cadena de certificados (FNMT, ACCV, AEAT)
	blocks, err := stdPkcs12.ToPEM(p12Data, password)
	if err != nil {
		return nil, err
	}

	var privateKey *rsa.PrivateKey
	var certs []*x509.Certificate

	for _, b := range blocks {
		switch b.Type {
		case "PRIVATE KEY":
			key, err := x509.ParsePKCS8PrivateKey(b.Bytes)
			if err != nil {
				key2, err2 := x509.ParsePKCS1PrivateKey(b.Bytes)
				if err2 == nil {
					privateKey = key2
				}
			} else if rsaKey, ok := key.(*rsa.PrivateKey); ok {
				privateKey = rsaKey
			}
		case "CERTIFICATE":
			cert, err := x509.ParseCertificate(b.Bytes)
			if err == nil {
				certs = append(certs, cert)
			}
		}
	}

	if privateKey == nil || len(certs) == 0 {
		return nil, stdPkcs12.ErrIncorrectPassword
	}

	for _, c := range certs {
		if rsaPub, ok := c.PublicKey.(*rsa.PublicKey); ok {
			if rsaPub.N.Cmp(privateKey.PublicKey.N) == 0 {
				return c, nil
			}
		}
	}

	return certs[0], nil
}

// DecodeLeafAndKey extrae la clave privada y el certificado hoja de un P12.
// Usado por el cliente TLS para establecer conexiones mTLS.
func DecodeLeafAndKey(p12Data []byte, password string) (interface{}, *x509.Certificate, []*x509.Certificate, error) {
	key, cert, err := goPkcs12.Decode(p12Data, password)
	if err == nil {
		return key, cert, nil, nil
	}

	blocks, err := stdPkcs12.ToPEM(p12Data, password)
	if err != nil {
		return nil, nil, nil, err
	}

	var privateKey interface{}
	var leafCert *x509.Certificate
	var chain []*x509.Certificate

	for _, b := range blocks {
		switch b.Type {
		case "PRIVATE KEY":
			k, err := x509.ParsePKCS8PrivateKey(b.Bytes)
			if err != nil {
				k2, err2 := x509.ParsePKCS1PrivateKey(b.Bytes)
				if err2 == nil {
					privateKey = k2
				}
			} else {
				privateKey = k
			}
		case "CERTIFICATE":
			c, err := x509.ParseCertificate(b.Bytes)
			if err == nil {
				chain = append(chain, c)
			}
		}
	}

	if privateKey == nil || len(chain) == 0 {
		return nil, nil, nil, stdPkcs12.ErrIncorrectPassword
	}

	if rsaKey, ok := privateKey.(*rsa.PrivateKey); ok {
		for i, c := range chain {
			if rsaPub, ok := c.PublicKey.(*rsa.PublicKey); ok {
				if rsaPub.N.Cmp(rsaKey.PublicKey.N) == 0 {
					leafCert = c
					chain = append(chain[:i], chain[i+1:]...)
					break
				}
			}
		}
	}

	if leafCert == nil {
		leafCert = chain[0]
		chain = chain[1:]
	}

	return privateKey, leafCert, chain, nil
}
