package soap

import (
	"bytes"
	"context"
	"encoding/xml"
	"io"
	"net/http"
	"time"

	"github.com/google/uuid"
	"github.com/pkg/errors"
)

// Client es la interfaz para un cliente SOAP
type Client interface {
	Call(ctx context.Context, operation string, request interface{}, response interface{}) error
}

// ClientOptions contiene opciones para el cliente SOAP
type ClientOptions struct {
	Endpoint string
	Username string
	Password string
	Timeout  time.Duration
	Headers  map[string]string
}

// RequestIDKey es la clave para el ID de solicitud en el contexto
type RequestIDKey struct{}

// client implementa la interfaz Client
//type client struct {
//	endpoint    string
//	httpClient  *http.Client
//	credentials *credentials
//	headers     map[string]string
//}

// credentials contiene las credenciales para autenticación
type credentials struct {
	username string
	password string
}

// Añadir este método al final del archivo pkg/soap/soap.go
// En la definición del cliente:
type client struct {
	endpoint    string
	httpClient  *http.Client
	credentials *credentials
	headers     map[string]string
}

// Endpoint retorna el endpoint actual del cliente
func (c *client) Endpoint() string {
	return c.endpoint
}

// NewClient crea un nuevo cliente SOAP
func NewClient(options ClientOptions) Client {
	httpClient := &http.Client{
		Timeout: options.Timeout,
	}

	var creds *credentials
	if options.Username != "" && options.Password != "" {
		creds = &credentials{
			username: options.Username,
			password: options.Password,
		}
	}

	return &client{
		endpoint:    options.Endpoint,
		httpClient:  httpClient,
		credentials: creds,
		headers:     options.Headers,
	}
}

// Envelope es la estructura para el envelope SOAP
type Envelope struct {
	XMLName xml.Name    `xml:"Envelope"`
	XMLNS   string      `xml:"xmlns,attr"`
	Header  interface{} `xml:"Header,omitempty"`
	Body    Body        `xml:"Body"`
}

// Body es la estructura para el cuerpo SOAP
type Body struct {
	XMLName xml.Name    `xml:"Body"`
	Content interface{} `xml:",omitempty"`
	Fault   *Fault      `xml:"Fault,omitempty"`
}

// Fault es la estructura para errores SOAP
type Fault struct {
	XMLName     xml.Name `xml:"Fault"`
	FaultCode   string   `xml:"faultcode,omitempty"`
	FaultString string   `xml:"faultstring,omitempty"`
	Detail      string   `xml:"detail,omitempty"`
}

// Call realiza una llamada SOAP
func (c *client) Call(ctx context.Context, operation string, request interface{}, response interface{}) error {
	// Obtener ID de solicitud del contexto o generar uno nuevo
	var requestID string
	if id, ok := ctx.Value(RequestIDKey{}).(string); ok {
		requestID = id
	} else {
		requestID = uuid.New().String()
	}

	// Crear envelope SOAP
	envelope := Envelope{
		XMLNS: "http://schemas.xmlsoap.org/soap/envelope/",
		Body: Body{
			Content: request,
		},
	}

	// Serializar envelope a XML
	requestBuffer := &bytes.Buffer{}
	enc := xml.NewEncoder(requestBuffer)
	enc.Indent("", "  ")
	if err := enc.Encode(envelope); err != nil {
		return errors.Wrap(err, "error encoding SOAP request")
	}

	// Crear solicitud HTTP
	httpReq, err := http.NewRequestWithContext(
		ctx,
		http.MethodPost,
		c.endpoint,
		requestBuffer,
	)
	if err != nil {
		return errors.Wrap(err, "error creating HTTP request")
	}

	// Establecer cabeceras HTTP
	httpReq.Header.Set("Content-Type", "text/xml; charset=utf-8")
	httpReq.Header.Set("SOAPAction", operation)
	httpReq.Header.Set("X-Request-ID", requestID)

	// Agregar cabeceras adicionales
	for key, value := range c.headers {
		httpReq.Header.Set(key, value)
	}

	// Agregar autenticación si es necesario
	if c.credentials != nil {
		httpReq.SetBasicAuth(c.credentials.username, c.credentials.password)
	}

	// Realizar solicitud HTTP
	httpResp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return errors.Wrap(err, "error sending HTTP request")
	}
	defer httpResp.Body.Close()

	// Leer respuesta
	respBody, err := io.ReadAll(httpResp.Body)
	if err != nil {
		return errors.Wrap(err, "error reading HTTP response")
	}

	// Verificar código de estado HTTP
	if httpResp.StatusCode != http.StatusOK {
		return errors.Errorf("HTTP error: %d - %s, body: %s",
			httpResp.StatusCode,
			httpResp.Status,
			string(respBody),
		)
	}

	// Deserializar respuesta
	respEnvelope := &Envelope{
		Body: Body{
			Content: response,
		},
	}

	if err := xml.Unmarshal(respBody, respEnvelope); err != nil {
		return errors.Wrap(err, "error unmarshalling SOAP response")
	}

	// Verificar si hay un SOAP Fault
	if respEnvelope.Body.Fault != nil {
		return errors.Errorf("SOAP Fault: %s - %s, detail: %s",
			respEnvelope.Body.Fault.FaultCode,
			respEnvelope.Body.Fault.FaultString,
			respEnvelope.Body.Fault.Detail,
		)
	}

	return nil
}
