package main

import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/sha1"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"net/http"
	"time"
)

// SOAP Envelope structures
type Envelope struct {
	XMLName xml.Name `xml:"http://www.w3.org/2003/05/soap-envelope Envelope"`
	Header  *Header  `xml:"http://www.w3.org/2003/05/soap-envelope Header,omitempty"`
	Body    Body     `xml:"http://www.w3.org/2003/05/soap-envelope Body"`
}

type Header struct {
	Security *Security `xml:"Security,omitempty"`
}

type Body struct {
	Content interface{} `xml:",omitempty"`
}

type Security struct {
	XMLName        xml.Name       `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd Security"`
	MustUnderstand string         `xml:"http://www.w3.org/2003/05/soap-envelope mustUnderstand,attr,omitempty"`
	UsernameToken  *UsernameToken `xml:"UsernameToken,omitempty"`
}

type UsernameToken struct {
	XMLName  xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd UsernameToken"`
	Username string   `xml:"Username"`
	Password Password `xml:"Password"`
	Nonce    Nonce    `xml:"Nonce"`
	Created  string   `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd Created"`
}

type Password struct {
	Type     string `xml:"Type,attr"`
	Password string `xml:",chardata"`
}

type Nonce struct {
	Type  string `xml:"EncodingType,attr"`
	Nonce string `xml:",chardata"`
}

type GetDeviceInformation struct {
	XMLName xml.Name `xml:"tds:GetDeviceInformation"`
	Xmlns   string   `xml:"xmlns:tds,attr"`
}

func createSecurityHeader(username, password string) *Security {
	nonceBytes := make([]byte, 16)
	_, _ = rand.Read(nonceBytes)
	nonce := base64.StdEncoding.EncodeToString(nonceBytes)

	created := time.Now().UTC().Format(time.RFC3339)

	hash := sha1.New()
	hash.Write(nonceBytes)
	hash.Write([]byte(created))
	hash.Write([]byte(password))
	digest := base64.StdEncoding.EncodeToString(hash.Sum(nil))

	return &Security{
		MustUnderstand: "1",
		UsernameToken: &UsernameToken{
			Username: username,
			Password: Password{
				Type:     "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest",
				Password: digest,
			},
			Nonce: Nonce{
				Type:  "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary",
				Nonce: nonce,
			},
			Created: created,
		},
	}
}

func main() {
	endpoint := "http://192.168.1.201/onvif/device_service"
	username := "service"
	password := "Service.1234"

	fmt.Println("Testing direct SOAP request to camera...")

	// Build request
	req := GetDeviceInformation{
		Xmlns: "http://www.onvif.org/ver10/device/wsdl",
	}

	envelope := &Envelope{
		Header: &Header{
			Security: createSecurityHeader(username, password),
		},
		Body: Body{
			Content: req,
		},
	}

	// Marshal to XML
	body, err := xml.MarshalIndent(envelope, "", "  ")
	if err != nil {
		log.Fatalf("Failed to marshal: %v", err)
	}

	xmlBody := append([]byte(xml.Header), body...)

	fmt.Println("\n=== Request XML ===")
	fmt.Println(string(xmlBody))

	// Create HTTP request
	httpReq, err := http.NewRequestWithContext(context.Background(), "POST", endpoint, bytes.NewReader(xmlBody))
	if err != nil {
		log.Fatalf("Failed to create request: %v", err)
	}

	httpReq.Header.Set("Content-Type", "application/soap+xml; charset=utf-8")

	// Send request
	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(httpReq)
	if err != nil {
		log.Fatalf("Failed to send request: %v", err)
	}
	defer func() { _ = resp.Body.Close() }()

	// Read response
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatalf("Failed to read response: %v", err)
	}

	fmt.Printf("\n=== HTTP Status: %d ===\n", resp.StatusCode)
	fmt.Printf("\n=== Response Headers ===\n")
	for k, v := range resp.Header {
		fmt.Printf("%s: %v\n", k, v)
	}
	fmt.Printf("\n=== Response Body ===\n")
	fmt.Println(string(respBody))
}
