package cert

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"math/big"
	"testing"
	"time"

	"software.sslmate.com/src/go-pkcs12"
)

const testPassword = "test-password-123"

// generarP12 crea un certificado autofirmado y lo empaqueta en P12.
// notBefore y notAfter controlan la validez del certificado.
func generarP12(t *testing.T, cn string, notBefore, notAfter time.Time) []byte {
	t.Helper()

	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatalf("generando clave RSA: %v", err)
	}

	template := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		Subject:      pkix.Name{CommonName: cn},
		NotBefore:    notBefore,
		NotAfter:     notAfter,
	}

	certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
	if err != nil {
		t.Fatalf("creando certificado: %v", err)
	}

	x509Cert, err := x509.ParseCertificate(certDER)
	if err != nil {
		t.Fatalf("parseando certificado: %v", err)
	}

	p12Data, err := pkcs12.Modern.Encode(key, x509Cert, nil, testPassword)
	if err != nil {
		t.Fatalf("codificando P12: %v", err)
	}

	return p12Data
}

func TestValidateP12Bytes_CertificadoValido365Dias(t *testing.T) {
	now := time.Now()
	p12 := generarP12(t, "Valid 365 days", now.Add(-time.Hour), now.Add(365*24*time.Hour))

	result := ValidateP12Bytes(p12, testPassword)

	if !result.Valid {
		t.Errorf("esperado válido, obtuvo error: %s", result.Error)
	}
	if result.Error != "" {
		t.Errorf("no debería haber error, obtuvo: %s", result.Error)
	}
	if len(result.Warnings) > 0 {
		t.Errorf("no debería haber warnings para cert de 365 días, obtuvo: %v", result.Warnings)
	}
	if result.CertInfo == nil {
		t.Fatal("CertInfo no debería ser nil para cert válido")
	}
	if result.CertInfo.DaysUntilExpiry < 360 {
		t.Errorf("días hasta expiración: esperado ~365, obtuvo %d", result.CertInfo.DaysUntilExpiry)
	}
}

func TestValidateP12Bytes_CertificadoValido60Dias(t *testing.T) {
	now := time.Now()
	p12 := generarP12(t, "Valid 60 days", now.Add(-time.Hour), now.Add(60*24*time.Hour))

	result := ValidateP12Bytes(p12, testPassword)

	if !result.Valid {
		t.Errorf("esperado válido, obtuvo error: %s", result.Error)
	}
	if len(result.Warnings) > 0 {
		t.Errorf("no debería haber warnings para cert de 60 días, obtuvo: %v", result.Warnings)
	}
	if result.CertInfo.DaysUntilExpiry < 55 || result.CertInfo.DaysUntilExpiry > 65 {
		t.Errorf("días hasta expiración: esperado ~60, obtuvo %d", result.CertInfo.DaysUntilExpiry)
	}
}

func TestValidateP12Bytes_CertificadoExpirado(t *testing.T) {
	now := time.Now()
	p12 := generarP12(t, "Expired", now.Add(-20*24*time.Hour), now.Add(-5*24*time.Hour))

	result := ValidateP12Bytes(p12, testPassword)

	if result.Valid {
		t.Error("esperado inválido para certificado expirado")
	}
	if result.Error != "certificate_expired" {
		t.Errorf("error esperado: certificate_expired, obtuvo: %s", result.Error)
	}
	if result.CertInfo == nil || !result.CertInfo.Expired {
		t.Error("CertInfo.Expired debería ser true")
	}
}

func TestValidateP12Bytes_CertificadoCaducaProximamente(t *testing.T) {
	now := time.Now()
	p12 := generarP12(t, "Expiring Soon", now.Add(-time.Hour), now.Add(15*24*time.Hour))

	result := ValidateP12Bytes(p12, testPassword)

	if !result.Valid {
		t.Errorf("esperado válido con warning, obtuvo error: %s", result.Error)
	}
	if len(result.Warnings) == 0 {
		t.Error("esperado warning certificate_expiring_soon para cert que caduca en 15 días")
	}
	if len(result.Warnings) > 0 && result.Warnings[0] != "certificate_expiring_soon" {
		t.Errorf("warning esperado: certificate_expiring_soon, obtuvo: %s", result.Warnings[0])
	}
	if result.CertInfo == nil || !result.CertInfo.ExpiringSoon {
		t.Error("CertInfo.ExpiringSoon debería ser true")
	}
}

func TestValidateP12Bytes_CertificadoAunNoValido(t *testing.T) {
	now := time.Now()
	p12 := generarP12(t, "Not Yet Valid", now.Add(30*24*time.Hour), now.Add(395*24*time.Hour))

	result := ValidateP12Bytes(p12, testPassword)

	if result.Valid {
		t.Error("esperado inválido para certificado aún no vigente")
	}
	if result.Error != "certificate_not_yet_valid" {
		t.Errorf("error esperado: certificate_not_yet_valid, obtuvo: %s", result.Error)
	}
}

func TestValidateP12Bytes_ContraseñaIncorrecta(t *testing.T) {
	now := time.Now()
	p12 := generarP12(t, "Valid Cert", now.Add(-time.Hour), now.Add(365*24*time.Hour))

	result := ValidateP12Bytes(p12, "contraseña-incorrecta")

	if result.Valid {
		t.Error("esperado inválido con contraseña incorrecta")
	}
	if result.Error != "invalid_password_or_format" {
		t.Errorf("error esperado: invalid_password_or_format, obtuvo: %s", result.Error)
	}
}

func TestValidateP12Bytes_BytesCorruptos(t *testing.T) {
	result := ValidateP12Bytes([]byte("esto no es un P12 valido"), testPassword)

	if result.Valid {
		t.Error("esperado inválido para bytes corruptos")
	}
	if result.Error != "invalid_password_or_format" {
		t.Errorf("error esperado: invalid_password_or_format, obtuvo: %s", result.Error)
	}
}

func TestValidateP12Bytes_UmbralWarning30Dias(t *testing.T) {
	now := time.Now()

	// Exactamente en el umbral: 30 días → debe dar warning
	p12Umbral := generarP12(t, "At threshold", now.Add(-time.Hour), now.Add(30*24*time.Hour))
	resultUmbral := ValidateP12Bytes(p12Umbral, testPassword)
	if !resultUmbral.Valid {
		t.Errorf("cert en umbral debe ser válido, obtuvo error: %s", resultUmbral.Error)
	}
	if len(resultUmbral.Warnings) == 0 {
		t.Error("cert con exactamente 30 días debe tener warning")
	}

	// 31 días → NO debe dar warning
	p12Ok := generarP12(t, "Just above threshold", now.Add(-time.Hour), now.Add(31*24*time.Hour))
	resultOk := ValidateP12Bytes(p12Ok, testPassword)
	if !resultOk.Valid {
		t.Errorf("cert con 31 días debe ser válido, obtuvo error: %s", resultOk.Error)
	}
	if len(resultOk.Warnings) > 0 {
		t.Error("cert con 31 días NO debe tener warning")
	}
}

func TestValidateP12_ArchivoNoExiste(t *testing.T) {
	result := ValidateP12("/ruta/que/no/existe.p12", testPassword)

	if result.Valid {
		t.Error("esperado inválido para archivo inexistente")
	}
	if result.Error != "file_not_found" {
		t.Errorf("error esperado: file_not_found, obtuvo: %s", result.Error)
	}
}
