package caddytest

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"testing"

	"github.com/aryann/difflib"
	"github.com/stretchr/testify/require"

	"github.com/caddyserver/caddy/v2/caddyconfig"
)

// AssertLoadError will load a config and expect an error
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
	tc, err := NewTester()
	require.NoError(t, err)
	err = tc.LaunchCaddy()
	require.NoError(t, err)

	err = tc.LoadConfig(rawConfig, configType)
	if !strings.Contains(err.Error(), expectedError) {
		t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
	}
	_ = tc.CleanupCaddy()
}

// CompareAdapt adapts a config and then compares it against an expected result
func CompareAdapt(t testing.TB, filename, rawConfig string, adapterName string, expectedResponse string) bool {
	cfgAdapter := caddyconfig.GetAdapter(adapterName)
	if cfgAdapter == nil {
		t.Logf("unrecognized config adapter '%s'", adapterName)
		return false
	}

	options := make(map[string]any)

	result, warnings, err := cfgAdapter.Adapt([]byte(rawConfig), options)
	if err != nil {
		t.Logf("adapting config using %s adapter: %v", adapterName, err)
		return false
	}

	// prettify results to keep tests human-manageable
	var prettyBuf bytes.Buffer
	err = json.Indent(&prettyBuf, result, "", "\t")
	if err != nil {
		return false
	}
	result = prettyBuf.Bytes()

	if len(warnings) > 0 {
		for _, w := range warnings {
			t.Logf("warning: %s:%d: %s: %s", filename, w.Line, w.Directive, w.Message)
		}
	}

	diff := difflib.Diff(
		strings.Split(expectedResponse, "\n"),
		strings.Split(string(result), "\n"))

	// scan for failure
	failed := false
	for _, d := range diff {
		if d.Delta != difflib.Common {
			failed = true
			break
		}
	}

	if failed {
		for _, d := range diff {
			switch d.Delta {
			case difflib.Common:
				fmt.Printf("  %s\n", d.Payload)
			case difflib.LeftOnly:
				fmt.Printf(" - %s\n", d.Payload)
			case difflib.RightOnly:
				fmt.Printf(" + %s\n", d.Payload)
			}
		}
		return false
	}
	return true
}

// AssertAdapt adapts a config and then tests it against an expected result
func AssertAdapt(t testing.TB, rawConfig string, adapterName string, expectedResponse string) {
	ok := CompareAdapt(t, "Caddyfile", rawConfig, adapterName, expectedResponse)
	if !ok {
		t.Fail()
	}
}

// Generic request functions

func applyHeaders(t testing.TB, req *http.Request, requestHeaders []string) {
	requestContentType := ""
	for _, requestHeader := range requestHeaders {
		arr := strings.SplitAfterN(requestHeader, ":", 2)
		k := strings.TrimRight(arr[0], ":")
		v := strings.TrimSpace(arr[1])
		if k == "Content-Type" {
			requestContentType = v
		}
		t.Logf("Request header: %s => %s", k, v)
		req.Header.Set(k, v)
	}

	if requestContentType == "" {
		t.Logf("Content-Type header not provided")
	}
}