package arrange

import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"testing"
	"time"

	bulkclient "github.com/nexuscrm/tests/contract/bulk_api_v2/client"
	"github.com/nexuscrm/tests/contract/internal/liveapi"
	"github.com/nexuscrm/tests/contract/internal/providerbootstrap"
	"github.com/stretchr/testify/require"
)

const waitTimeout = 30 * time.Second

var ensureLocalStandardObjectsOnce sync.Once

func EnsureLocalStandardObjects(t testing.TB) {
	t.Helper()

	cfg := liveapi.LoadConfig(t)
	if cfg.TargetName != "local" {
		return
	}

	ensureLocalStandardObjectsOnce.Do(func() {
		lockPath := filepath.Join(os.TempDir(), "nexuscrm-contract-local-bootstrap.lock")
		lockFile, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
		require.NoError(t, err)
		defer lockFile.Close()

		require.NoError(t, syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX))
		defer func() {
			_ = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_UN)
		}()

		scriptPath, err := providerbootstrap.BaselineScriptPath()
		require.NoError(t, err)

		cmd := exec.Command("bash", scriptPath)
		cmd.Env = append(cmd.Environ(),
			"LOCAL_BACKEND_BASE_URL="+cfg.RootBaseURL,
			"LOCAL_BACKEND_TOKEN_URL="+cfg.TokenURL,
			"REST_API_API_VERSION="+cfg.APIVersion,
			"LOCAL_BACKEND_CLIENT_ID="+cfg.ClientID,
			"LOCAL_BACKEND_CLIENT_SECRET="+cfg.ClientSecret,
		)
		output, err := cmd.CombinedOutput()
		require.NoError(t, err, "initialize local standard objects: %s", strings.TrimSpace(string(output)))
	})
}

func CreateQueryJob(t testing.TB, c *bulkclient.Client, req bulkclient.CreateQueryJobRequest) bulkclient.BulkJob {
	t.Helper()

	resp, err := c.CreateQueryJob(req)
	require.NoError(t, err)
	require.Contains(t, []int{http.StatusOK, http.StatusCreated}, resp.StatusCode)
	return DecodeJob(t, resp)
}

func WaitForQueryState(t testing.TB, c *bulkclient.Client, jobID string, allowed ...string) bulkclient.BulkJob {
	t.Helper()

	deadline := time.Now().Add(waitTimeout)
	for time.Now().Before(deadline) {
		resp, err := c.GetQueryJob(jobID)
		require.NoError(t, err)
		require.Equal(t, http.StatusOK, resp.StatusCode)
		job := DecodeJob(t, resp)
		for _, state := range allowed {
			if job.State == state {
				return job
			}
		}
		time.Sleep(500 * time.Millisecond)
	}
	t.Fatalf("query job %s did not reach any expected state %v", jobID, allowed)
	return bulkclient.BulkJob{}
}

func CreateIngestJob(t testing.TB, c *bulkclient.Client, req bulkclient.CreateIngestJobRequest) bulkclient.BulkJob {
	t.Helper()

	resp, err := c.CreateIngestJob(req)
	require.NoError(t, err)
	require.Equal(t, http.StatusCreated, resp.StatusCode)
	return DecodeJob(t, resp)
}

func DecodeJob(t testing.TB, resp *http.Response) bulkclient.BulkJob {
	t.Helper()
	defer resp.Body.Close()

	var job bulkclient.BulkJob
	require.NoError(t, json.NewDecoder(resp.Body).Decode(&job))
	return job
}

func DecodeErrorArray(t testing.TB, resp *http.Response) []bulkclient.ErrorMessage {
	t.Helper()
	defer resp.Body.Close()

	var errs []bulkclient.ErrorMessage
	require.NoError(t, json.NewDecoder(resp.Body).Decode(&errs))
	return errs
}

func ReadBody(t testing.TB, resp *http.Response) string {
	t.Helper()
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)
	return string(body)
}

func ReadCSV(t testing.TB, raw string) [][]string {
	t.Helper()

	rows, err := csv.NewReader(strings.NewReader(raw)).ReadAll()
	require.NoError(t, err)
	require.NotEmpty(t, rows)
	return rows
}

func UniqueAccountName(prefix string) string {
	return fmt.Sprintf("%s-%d", prefix, time.Now().UnixNano())
}
