package cmd

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"

	"github.com/tidbcloud/clinic-cli/internal/clinic"
)

func diagnosticsEnabled(ctx *RuntimeContext) bool {
	return ctx != nil && ctx.Verbose && ctx.DiagnosticsOut != nil
}

func diagnosticf(ctx *RuntimeContext, format string, args ...any) {
	if !diagnosticsEnabled(ctx) {
		return
	}
	_, _ = fmt.Fprintf(ctx.DiagnosticsOut, "[clinic verbose] "+format+"\n", args...)
}

func warningf(ctx *RuntimeContext, format string, args ...any) {
	if ctx == nil || ctx.DiagnosticsOut == nil {
		return
	}
	_, _ = fmt.Fprintf(ctx.DiagnosticsOut, "[clinic warning] "+format+"\n", args...)
}

func printRuntimeDiagnostics(ctx *RuntimeContext) {
	if !diagnosticsEnabled(ctx) {
		return
	}

	if ctx.SelectedEnv == "" {
		diagnosticf(ctx, "selected env: <none>")
	} else {
		diagnosticf(ctx, "selected env: %q (source: %s)", ctx.SelectedEnv, valueOrFallback(ctx.SelectedEnvSource, "unknown"))
	}

	if ctx.SelectedProfileName == "" {
		diagnosticf(ctx, "selected profile: <none> (source: %s)", valueOrFallback(ctx.SelectedProfileSource, "none"))
	} else {
		diagnosticf(
			ctx,
			"selected profile: %q (source: %s)",
			ctx.SelectedProfileName,
			valueOrFallback(ctx.SelectedProfileSource, "unknown"),
		)
	}

	diagnosticf(
		ctx,
		"env vars: %s=%s, %s=%s, %s=%s",
		envClinicClusterID, valueOrUnset(rawEnvValue(envClinicClusterID)),
		envClinicRangeStart, valueOrUnset(rawEnvValue(envClinicRangeStart)),
		envClinicRangeEnd, valueOrUnset(rawEnvValue(envClinicRangeEnd)),
	)

	if ctx.SelectedProfile == nil {
		diagnosticf(ctx, "profile defaults: <none>")
		return
	}

	diagnosticf(
		ctx,
		"profile defaults: cluster_id=%q, range_start=%q, range_end=%q",
		ctx.SelectedProfile.ClusterID,
		ctx.SelectedProfile.RangeStart,
		ctx.SelectedProfile.RangeEnd,
	)
}

func valueOrFallback(value, fallback string) string {
	if value == "" {
		return fallback
	}
	return value
}

func valueOrUnset(value string) string {
	if value == "" {
		return "<unset>"
	}
	return fmt.Sprintf("%q", value)
}

func doClinicRequest(ctx *RuntimeContext, req clinic.Request, out any) error {
	if ctx == nil || ctx.Client == nil {
		return errors.New("clinic client not configured")
	}

	logRequestDiagnostics(ctx, req)
	return ctx.Client.Do(context.Background(), req, out)
}

func logRequestDiagnostics(ctx *RuntimeContext, req clinic.Request) {
	if !diagnosticsEnabled(ctx) {
		return
	}

	diagnosticf(ctx, "request: %s %s", req.Method, requestPathWithQuery(req))
	if encoded := req.Query.Encode(); encoded != "" {
		diagnosticf(ctx, "request query: %s", encoded)
	}
	if rawQuery := req.Query.Get("query"); rawQuery != "" {
		diagnosticf(ctx, "request query[query]: %s", rawQuery)
	}
	if len(req.Headers) > 0 {
		diagnosticf(ctx, "request headers: %s", marshalForLog(req.Headers))
	}
	if req.Body != nil {
		diagnosticf(ctx, "request body: %s", marshalForLog(req.Body))
	}
}

func requestPathWithQuery(req clinic.Request) string {
	encoded := req.Query.Encode()
	if encoded == "" {
		return req.Path
	}
	return req.Path + "?" + encoded
}

func marshalForLog(value any) string {
	data, err := json.Marshal(value)
	if err != nil {
		return fmt.Sprintf("%#v", value)
	}
	return string(data)
}
