// validation_rest.go posts XML content to a remote validation API and prints
// the HTTP response.
//
// Version: 1.0.0 (2026-06-28T06:59Z)
// Author: David Ireland <https://di-mgt.com.au/contact>
// Copyright: 2026 DI Management Services Pty Ltd
// SPDX-License-Identifier: Apache-2.0
// Usage:
//
//    go run validation_rest.go
package main

import (
    "bytes"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
)

func read_text_file(fname string) string {
    data, err := os.ReadFile(fname)
    if err != nil {
        panic(err)
    }
    // Cast bytes to string
    content := string(data)
    return content
}

// loadCert loads certificate data from either a file path or a certificate string.
// If certfileOrString is empty, it returns an empty string without error.
// If certfileOrString refers to a readable file, the file is loaded and returned
// either as base64-encoded bytes for binary certs or as raw text for PEM/base64 files.
// If certfileOrString is long and does not exist as a file, it is assumed to be
// a literal certificate string.
func loadCert(certfileOrString string) (string, error) {
    if certfileOrString == "" {
        return "", nil
    }
    // Not a filename so assume a plain base64 or PEM string
    // (we'll catch any errors later)
    if len(certfileOrString) > 250 {
        if _, err := os.Stat(certfileOrString); os.IsNotExist(err) {
            return certfileOrString, nil
        }
    }

    if _, err := os.Stat(certfileOrString); err != nil {
        if os.IsNotExist(err) {
            return "", fmt.Errorf("file %q not found", certfileOrString)
        }
        return "", err
    }
    // Read in the file contents
    data, err := os.ReadFile(certfileOrString)
    if err != nil {
        return "", err
    }
    if len(data) == 0 {
        return "", fmt.Errorf("%q is empty", certfileOrString)
    }
    // Certificate file could be binary .cer or textual PEM .crt or plain base64
    // Examine first byte to guess at form
    switch data[0] {
    case '0': // binary
        return base64.StdEncoding.EncodeToString(data), nil
    case 'M', '-': // Base64 or PEM
        return string(data), nil
    default:
        return "", fmt.Errorf("%q is not a valid certificate", certfileOrString)
    }
}

// validate posts XML content to the FirmaSAT validation REST API.
// xmlfile is the input XML filename and certfile_or_string is an optional
// PAC certificate file path or certificate string. The function reads the XML,
// optionally loads the certificate, sends the JSON payload, and prints the API
// response with pretty-printed JSON.
func validate(xmlfile string, certfile_or_string string) {
    // Local constants
    url := "https://di-mgt.com.au/cryptosys/api/fsa_validation"
    api_token := "YOUR_API_TOKEN_HERE" // Replace with your own api-token

    fmt.Printf("FILE: %s\n", xmlfile)

    // 1. Read in the XML content
    xmlContent := read_text_file(xmlfile)

    // Optional pac-cert file (or string)
    certContent := ""
    if certfile_or_string != "" {
        var err error
        certContent, err = loadCert(certfile_or_string)
        if err != nil {
            log.Fatal(err)
        }
    }

    // 2. Create a JSON payload containing the XML string
    payload := map[string]string{
        "api-token": api_token,
        "xmldata":   xmlContent,
        "pac-cert":  certContent,
    }

    // 3. Marshal the payload into JSON bytes
    jsonData, err := json.Marshal(payload)
    if err != nil {
        log.Fatal(err)
    }

    // 4. Create the HTTP POST request
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Set("Content-Type", "application/json")

    // 5. Execute the request
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    // Read the response body
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Status: %s\n", resp.Status)

    // Pretty-print the JSON response, coping with inner JSON object
    var pretty bytes.Buffer
    var responseData map[string]interface{}
    if err := json.Unmarshal(body, &responseData); err == nil {
        if vr, ok := responseData["validation-result"].(string); ok {
            var inner interface{}
            if err := json.Unmarshal([]byte(vr), &inner); err == nil {
                responseData["validation-result"] = inner
            }
        }
        if out, err := json.MarshalIndent(responseData, "", "  "); err == nil {
            fmt.Printf("Response body:\n%s\n", string(out))
        } else {
            fmt.Printf("Response body:\n%s\n", pretty.String())
        }
    } else if err := json.Indent(&pretty, body, "", "  "); err == nil {
        fmt.Printf("Response body:\n%s\n", pretty.String())
    } else {
        fmt.Printf("Response body:\n%s\n", string(body))
    }
}

func main() {
    // These two files must exist in the CWD
    validate("cfdv40-ejemplo-signed-tfd.xml", "pac.cer") // binary cert
}