package verifactu

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"strings"
	"time"
)

type SOAPEnvelope struct {
	XMLName   xml.Name `xml:"soap:Envelope"`
	XmlnsSOAP string   `xml:"xmlns:soap,attr"`
	XmlnsSUM  string   `xml:"xmlns:sum,attr"`
	XmlnsSUM1 string   `xml:"xmlns:sum1,attr"`
	Header    SOAPHeader
	Body      SOAPBody
}

type SOAPHeader struct {
	XMLName     xml.Name `xml:"soap:Header"`
	Security    *SecurityHeader
	Transaction string `xml:"wsa:Action,omitempty"`
}

type SecurityHeader struct {
	XMLName             xml.Name `xml:"wsse:Security"`
	XmlnsWSSE           string   `xml:"xmlns:wsse,attr"`
	XmlnsWSSEDSIG       string   `xml:"xmlns:wssedsig,attr"`
	BinarySecurityToken *BinarySecurityToken
	Signature           *Signature
}

type BinarySecurityToken struct {
	XMLName      xml.Name `xml:"wsse:BinarySecurityToken"`
	EncodingType string   `xml:"wsse:EncodingType,attr"`
	ValueType    string   `xml:"wsse:ValueType,attr"`
	Id           string   `xml:"wsu:Id,attr"`
	Content      string   `xml:",chardata"`
}

type Signature struct {
	XMLName        xml.Name `xml:"ds:Signature"`
	XmlnsDS        string   `xml:"xmlns:ds,attr"`
	Id             string   `xml:"Id,attr"`
	SignedInfo     SignedInfo
	SignatureValue string `xml:"ds:SignatureValue"`
	KeyInfo        KeyInfo
}

type SignedInfo struct {
	XMLName                xml.Name `xml:"ds:SignedInfo"`
	CanonicalizationMethod CanonicalMethod
	SignatureMethod        SignatureMethod
	References             []Reference
}

type CanonicalMethod struct {
	XMLName   xml.Name `xml:"ds:CanonicalizationMethod"`
	Algorithm string   `xml:"Algorithm,attr"`
}

type SignatureMethod struct {
	XMLName   xml.Name `xml:"ds:SignatureMethod"`
	Algorithm string   `xml:"Algorithm,attr"`
}

type Reference struct {
	XMLName      xml.Name `xml:"ds:Reference"`
	URI          string   `xml:"URI,attr"`
	DigestMethod DigestMethod
	DigestValue  string `xml:"ds:DigestValue"`
}

type DigestMethod struct {
	XMLName   xml.Name `xml:"ds:DigestMethod"`
	Algorithm string   `xml:"Algorithm,attr"`
}

type KeyInfo struct {
	XMLName          xml.Name `xml:"ds:KeyInfo"`
	SecurityTokenRef SecurityTokenRef
}

type SecurityTokenRef struct {
	XMLName   xml.Name `xml:"wsse:SecurityTokenReference"`
	Id        string   `xml:"Id,attr"`
	Reference Reference2
}

type Reference2 struct {
	XMLName   xml.Name `xml:"wsse:Reference"`
	URI       string   `xml:"URI,attr"`
	ValueType string   `xml:"ValueType,attr"`
}

type SOAPBody struct {
	XMLName xml.Name    `xml:"soap:Body"`
	Content interface{} `xml:",any"`
}

func BuildSOAPEnvelope(payload interface{}) *SOAPEnvelope {
	return &SOAPEnvelope{
		XmlnsSOAP: "http://schemas.xmlsoap.org/soap/envelope/",
		XmlnsSUM:  "https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd",
		XmlnsSUM1: "https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd",
		Body: SOAPBody{
			Content: payload,
		},
	}
}

func (e *SOAPEnvelope) ToXML() (string, error) {
	var buf bytes.Buffer
	enc := xml.NewEncoder(&buf)

	err := enc.Encode(e)
	if err != nil {
		return "", fmt.Errorf("encoding SOAP envelope: %w", err)
	}

	return buf.String(), nil
}

func (e *SOAPEnvelope) ToBytes() ([]byte, error) {
	return xml.Marshal(e)
}

type EnviarFacturaRequest struct {
	AltaReq *AltaRequest
}

type EnviarAnulacionRequest struct {
	AnulReq *AnulacionRequest
}

func BuildAltaSOAPRequest(data AltaData) (*SOAPEnvelope, error) {
	return BuildSOAPEnvelope(BuildAltaRequest(data)), nil
}

func BuildAnulacionSOAPRequest(data AltaData) (*SOAPEnvelope, error) {
	return BuildSOAPEnvelope(BuildAnulacionRequest(data)), nil
}

func ParseResponse(data []byte) (*Response, error) {
	var env Response
	err := xml.Unmarshal(data, &env)
	if err != nil {
		return nil, fmt.Errorf("unmarshaling response: %w", err)
	}

	return &env, nil
}

func FormatDateDDMMYYYY(date time.Time) string {
	return date.Format("02-01-2006")
}

func FormatDateTimeDDMMYYYYHHMM(date time.Time) string {
	return date.Format("02-01-2006T15:04:05")
}

func StripXMLNamespace(data []byte) []byte {
	str := string(data)
	str = strings.ReplaceAll(str, "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"", "")
	str = strings.ReplaceAll(str, "xmlns:sum=\"https://www.agenciatributaria.gob.es/static/files/common/xsd/sum/information.xsd\"", "")
	str = strings.ReplaceAll(str, "xmlns:sum1=\"https://www.agenciatributaria.gob.es/static/files/common/xsd/sum/suministroInformacion.xsd\"", "")
	return []byte(str)
}
