package generadorFirmaDigital

import (
	"bytes"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/beevik/etree"
	dsig "github.com/russellhaering/goxmldsig"
)

// -----------------------------------------------------
// implementa dsig.X509KeyStore
// -----------------------------------------------------
type MyKeyStore struct {
	PrivateKey *rsa.PrivateKey
	CertBytes  []byte
}

func (ks *MyKeyStore) GetKeyPair() (*rsa.PrivateKey, []byte, error) {
	return ks.PrivateKey, ks.CertBytes, nil
}

// -----------------------------------------------------
// lee un PEM (PKCS#8 o PKCS#1) y retorna *rsa.PrivateKey
// -----------------------------------------------------
func CargarLlavePrivada(path string) (*rsa.PrivateKey, error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	block, _ := pem.Decode(data)
	if block == nil || !strings.Contains(block.Type, "PRIVATE KEY") {
		return nil, fmt.Errorf("clave PEM inválida")
	}
	// Intentar PKCS#8
	if keyIfc, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
		if priv, ok := keyIfc.(*rsa.PrivateKey); ok {
			return priv, nil
		}
		return nil, fmt.Errorf("no es llave RSA válida")
	}
	// Fallback a PKCS#1
	return x509.ParsePKCS1PrivateKey(block.Bytes)
}

// -----------------------------------------------------
// lee un PEM de certificado X.509
// -----------------------------------------------------
func CargarCertificado(path string) (*x509.Certificate, error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	block, _ := pem.Decode(data)
	if block == nil || block.Type != "CERTIFICATE" {
		return nil, fmt.Errorf("certificado PEM inválido")
	}
	return x509.ParseCertificate(block.Bytes)
}

// -----------------------------------------------------
// Carga clave privada desde un string PEM
// -----------------------------------------------------
func CargarLlavePrivadaDesdeString(pemStr string) (*rsa.PrivateKey, error) {
	pemStr = strings.TrimSpace(pemStr)
	block, _ := pem.Decode([]byte(pemStr))
	if block == nil || !strings.Contains(block.Type, "PRIVATE KEY") {
		return nil, fmt.Errorf("clave PEM inválida")
	}
	// Intentar PKCS#8
	if keyIfc, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
		if priv, ok := keyIfc.(*rsa.PrivateKey); ok {
			return priv, nil
		}
		return nil, fmt.Errorf("no es llave RSA válida")
	}
	// Fallback a PKCS#1
	return x509.ParsePKCS1PrivateKey(block.Bytes)
}

// -----------------------------------------------------
// Carga certificado desde un string PEM
// -----------------------------------------------------
func CargarCertificadoDesdeString(pemStr string) (*x509.Certificate, error) {
	pemStr = strings.TrimSpace(pemStr)
	block, _ := pem.Decode([]byte(pemStr))
	if block == nil || block.Type != "CERTIFICATE" {
		return nil, fmt.Errorf("certificado PEM inválido")
	}
	return x509.ParseCertificate(block.Bytes)
}

// -----------------------------------------------------
// Parsea un XML desde bytes
// -----------------------------------------------------
func LeerXML(xmlBytes []byte) (*etree.Document, error) {
	doc := etree.NewDocument()
	if err := doc.ReadFromBytes(xmlBytes); err != nil {
		return nil, err
	}
	return doc, nil
}

// -----------------------------------------------------
// firma el XML (enveloped, RSA-SHA256), sin prefijo y C14N inclusiva con comentarios
// -----------------------------------------------------
func FirmarXML(xmlBytes []byte, priv *rsa.PrivateKey, cert *x509.Certificate) ([]byte, error) {
	// 1) Parsear el XML con etree
	doc := etree.NewDocument()
	if err := doc.ReadFromBytes(xmlBytes); err != nil {
		return nil, err
	}
	root := doc.Root()

	// 2) Crear nuestro KeyStore
	ks := &MyKeyStore{
		PrivateKey: priv,
		CertBytes:  cert.Raw,
	}

	// 3) Contexto de firmado
	ctx := dsig.NewDefaultSigningContext(ks)
	//    — usar namespace por defecto (sin prefijo "ds")
	ctx.Prefix = ""
	//    — canonicalización inclusiva con comentarios (C14N 1.0 #WithComments)
	ctx.Canonicalizer = dsig.MakeC14N10WithCommentsCanonicalizer()

	// 4) Firmar en modo enveloped
	signedElem, err := ctx.SignEnveloped(root)
	if err != nil {
		return nil, err
	}

	// 5) Serializar el documento firmado
	doc.SetRoot(signedElem)
	return doc.WriteToBytes()
}

// -----------------------------------------------------
// Convierte un objeto a XML usando etree (alternativa a JAXB en Java)
// -----------------------------------------------------
func ConvertirObjetoAXML(obj interface{}) ([]byte, error) {
	// Esta función es un placeholder - en Go normalmente se usaría
	// encoding/xml o alguna biblioteca específica en lugar de JAXB de Java
	// Ejemplo simplificado para demostración:
	var buf bytes.Buffer
	// Aquí iría la lógica real de conversión dependiendo de la estructura
	// encoding/xml puede manejar esto con struct tags para mapear campos
	return buf.Bytes(), nil
}
