//nolint:musttag
package dotnet

import (
	_ "embed"
	"encoding/xml"
	"fmt"
	"strconv"
	"strings"

	"github.com/vulncheck-oss/go-exploit/output"
)

//go:embed data/ReturnMessage.xml
var returnMessageSnippet string

const (
	LOSFormatter                = "LOSFormatter"
	BinaryFormatter             = "BinaryFormatter"
	SOAPFormatter               = "SOAPFormatter"
	SOAPFormatterWithExceptions = "SOAPFormatterWithExceptions"
)

func FormatLOS(input string) string {
	marker := "\xff"
	version := "\x01"
	token := string(byte(50))

	return marker + version + token + lengthPrefixedString(input)
}

// SOAP Formatter types and funcs.

type SOAPEnvelope struct {
	XMLName xml.Name `xml:"SOAP-ENV:Envelope"` // Specify the XML name with namespace
	Body    Body     `xml:"SOAP-ENV:Body"`     // Nested struct for Body

	Xsi           string `xml:"xmlns:xsi,attr"`
	Xsd           string `xml:"xmlns:xsd,attr"`
	SoapEnc       string `xml:"xmlns:SOAP-ENC,attr"`
	SoapEnv       string `xml:"xmlns:SOAP-ENV,attr"`
	Clr           string `xml:"xmlns:clr,attr"`
	EncodingStyle string `xml:"SOAP-ENV:encodingStyle,attr"`
}

type Body struct {
	Classes []any
}

type ClassDataNode struct { // dynamic element, needs everything defined manually
	XMLName     xml.Name
	ID          string     `xml:"id,attr"`
	Attrs       []xml.Attr `xml:",attr"`
	Content     string     `xml:",chardata"`
	MemberNodes []any
}

type MemberNode struct {
	XMLName xml.Name
	ID      string     `xml:"id,attr,omitempty"`
	HREF    string     `xml:"href,attr,omitempty"`
	XsiType string     `xml:"xsi:type,attr,omitempty"`
	XsiNull string     `xml:"xsi:null,attr,omitempty"`
	Content string     `xml:",innerxml"`
	Attrs   []xml.Attr `xml:",attr"`
}

func (memberNode *MemberNode) addAttribute(name string, value string) {
	memberNode.Attrs = append(memberNode.Attrs, xml.Attr{
		Name:  xml.Name{Local: name},
		Value: value,
	})
}

func (classData *ClassDataNode) addAttribute(name string, value string) {
	classData.Attrs = append(classData.Attrs, xml.Attr{
		Name:  xml.Name{Local: name},
		Value: value,
	})
}

func escapeTags(input string) string {
	// escaping only < and >, without this it encodes more than we'd like. this may cause issues but will address as needed
	escaped := strings.ReplaceAll(input, "<", "&lt;")

	return strings.ReplaceAll(escaped, ">", "&gt;")
}

func (body *Body) addClassWithMemberAndTypes(n int, record ClassWithMembersAndTypesRecord) bool {
	baseClassName := record.ClassInfo.GetBaseClassName()

	classData := ClassDataNode{}
	ns := fmt.Sprintf("a%d", n)
	classData.XMLName.Local = fmt.Sprintf("%s:%s", ns, baseClassName)
	classData.ID = fmt.Sprintf("ref-%d", record.ClassInfo.ObjectID)                                                                               // id attr set
	libURL := fmt.Sprintf("http://schemas.microsoft.com/clr/nsassem/%s/%s", record.ClassInfo.GetLeadingClassName(), record.BinaryLibrary.Library) // xmlns:aN attr value
	classData.addAttribute("xmlns:"+ns, libURL)                                                                                                   //xmlns:aN attr set
	// add members to the classData, makes a new element for each member
	memberCount := record.ClassInfo.MemberCount
	if memberCount != len(record.ClassInfo.MemberNames) || memberCount != len(record.MemberTypeInfo.BinaryTypes) {
		output.PrintfFrameworkError("member count mismatch: memberNames %q , binaryTypes %q, memberCount %d", record.ClassInfo.MemberNames, record.MemberTypeInfo.BinaryTypes, memberCount)

		return false
	}

	for memberN := range memberCount {
		recordMember, ok := record.MemberValues[memberN].(Record)
		if ok {
			memberNode, ok := recordMember.ToXML(record.ClassInfo, record.MemberTypeInfo, record.BinaryLibrary, memberN, ns)
			if !ok {
				output.PrintFrameworkError("Failed to process record into XML")

				return false
			}
			classData.MemberNodes = append(classData.MemberNodes, memberNode)

			continue
		}

		intValue, ok := record.MemberValues[memberN].(int)
		if ok {
			memberNode := MemberNode{}
			memberNode.XMLName.Local = record.ClassInfo.MemberNames[memberN]
			memberNode.Content = strconv.Itoa(intValue)

			classData.MemberNodes = append(classData.MemberNodes, memberNode)

			continue
		}

		boolValue, ok := record.MemberValues[memberN].(bool)
		if ok {
			memberNode := MemberNode{}
			memberNode.XMLName.Local = record.ClassInfo.MemberNames[memberN]
			memberNode.Content = strconv.FormatBool(boolValue)

			classData.MemberNodes = append(classData.MemberNodes, memberNode)

			continue
		}

		output.PrintFrameworkError("Invalid or unsupported member value provided")
	}

	// wrapping it up
	body.Classes = append(body.Classes, classData)

	return true
}

func FormatSOAP(records []Record) (string, SOAPEnvelope, bool) {
	envelope := SOAPEnvelope{
		Xsi:           "http://www.w3.org/2001/XMLSchema-instance",
		Xsd:           "http://www.w3.org/2001/XMLSchema",
		SoapEnc:       "http://schemas.xmlsoap.org/soap/encoding/",
		SoapEnv:       "http://schemas.xmlsoap.org/soap/envelope/",
		Clr:           "http://schemas.microsoft.com/soap/encoding/clr/1.0",
		EncodingStyle: "http://schemas.xmlsoap.org/soap/encoding/",
	}

	body := Body{}

	for n, record := range records {
		arraySingleStringRecord, ok := record.(ArraySinglePrimitiveRecord)
		if ok {
			arrayClassDataNode, _ := arraySingleStringRecord.ToXMLBespoke()
			body.Classes = append(body.Classes, arrayClassDataNode)
		}
		classWithMembersAndTypes, ok := record.(ClassWithMembersAndTypesRecord)
		if ok {
			if !body.addClassWithMemberAndTypes(n+1, classWithMembersAndTypes) {
				return "", SOAPEnvelope{}, false
			}
		}
		systemClassWithMembersAndTypes, ok := record.(SystemClassWithMembersAndTypesRecord)
		if ok {
			mscorlib := "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
			// convert it to the non-system  because it's basically the same thing but soap format needs a library
			binLib := BinaryLibraryRecord{Library: mscorlib} // hardcoded lib, don't need an ID because it won't get used
			tempClassRecord := ClassWithMembersAndTypesRecord{
				ClassInfo:      systemClassWithMembersAndTypes.ClassInfo,
				MemberTypeInfo: systemClassWithMembersAndTypes.MemberTypeInfo,
				MemberValues:   systemClassWithMembersAndTypes.MemberValues,
				BinaryLibrary:  binLib,
			}
			if !body.addClassWithMemberAndTypes(n+1, tempClassRecord) {
				return "", SOAPEnvelope{}, false
			}
		}
	}

	envelope.Body = body

	xmlData, err := xml.Marshal(envelope)
	if err != nil {
		output.PrintfFrameworkError("Error marshaling to XML: %s", err)

		return "", SOAPEnvelope{}, false
	}

	output.PrintfFrameworkDebug("Generated via SOAPFormatter: %s", string(xmlData))

	return string(xmlData), envelope, true
}

// used for rogue reporting server SOAP messages.
func FormatSOAPWithExceptions(records []Record) (string, bool) {
	xmlData, _, ok := FormatSOAP(records)
	if !ok {
		return "", false
	}
	// quick and dirty replace but it's a static string, so should be fine. we're just adding them as a member of the body
	xmlData = strings.ReplaceAll(xmlData, "<SOAP-ENV:Body>", "<SOAP-ENV:Body>"+returnMessageSnippet)

	return xmlData, true
}
