// © Broadcom. All Rights Reserved.
// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
// SPDX-License-Identifier: Apache-2.0

package soap

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httputil"
	"sync/atomic"

	"github.com/vmware/govmomi/vim25/debug"
)

var (
	// Trace reads an http request or response from rc and writes to w.
	// The content type (kind) should be one of "xml" or "json".
	Trace = func(rc io.ReadCloser, w io.Writer, kind string) io.ReadCloser {
		return debug.NewTeeReader(rc, w)
	}
)

// debugRoundTrip contains state and logic needed to debug a single round trip.
type debugRoundTrip struct {
	cn uint64      // Client number
	rn uint64      // Request number
	cs []io.Closer // Files that need closing when done
}

func (d *debugRoundTrip) enabled() bool {
	return d != nil
}

func (d *debugRoundTrip) done() {
	for _, c := range d.cs {
		c.Close()
	}
}

func (d *debugRoundTrip) newFile(suffix string) io.WriteCloser {
	return debug.NewFile(fmt.Sprintf("%d-%04d.%s", d.cn, d.rn, suffix))
}

func (d *debugRoundTrip) ext(h http.Header) string {
	const json = "application/json"
	ext := "xml"
	if h.Get("Accept") == json || h.Get("Content-Type") == json {
		ext = "json"
	}
	return ext
}

func (d *debugRoundTrip) debugRequest(req *http.Request) string {
	if d == nil {
		return ""
	}

	// Capture headers
	var wc io.WriteCloser = d.newFile("req.headers")
	b, _ := httputil.DumpRequest(req, false)
	wc.Write(b)
	wc.Close()

	ext := d.ext(req.Header)
	// Capture body
	wc = d.newFile("req." + ext)
	if req.Body != nil {
		req.Body = Trace(req.Body, wc, ext)
	}

	// Delay closing until marked done
	d.cs = append(d.cs, wc)

	return ext
}

func (d *debugRoundTrip) debugResponse(res *http.Response, ext string) {
	if d == nil {
		return
	}

	// Capture headers
	var wc io.WriteCloser = d.newFile("res.headers")
	b, _ := httputil.DumpResponse(res, false)
	wc.Write(b)
	wc.Close()

	// Capture body
	wc = d.newFile("res." + ext)
	res.Body = Trace(res.Body, wc, ext)

	// Delay closing until marked done
	d.cs = append(d.cs, wc)
}

var cn uint64 // Client counter

// debugContainer wraps the debugging state for a single client.
type debugContainer struct {
	cn uint64 // Client number
	rn uint64 // Request counter
}

func newDebug() *debugContainer {
	d := debugContainer{
		cn: atomic.AddUint64(&cn, 1),
		rn: 0,
	}

	if !debug.Enabled() {
		return nil
	}
	return &d
}

func (d *debugContainer) newRoundTrip() *debugRoundTrip {
	if d == nil {
		return nil
	}

	drt := debugRoundTrip{
		cn: d.cn,
		rn: atomic.AddUint64(&d.rn, 1),
	}

	return &drt
}
