package cmd

import (
	"errors"
	"io"
	"net/http"
	"net/url"

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

func dataCommandHandler(ctx *RuntimeContext, args []string, stdout io.Writer) error {
	command := newDataCommand(staticRuntime(ctx), stdout)
	if err := executeCobraCommand(command, args); err != nil {
		return normalizeFamilyCommandError(err, "data")
	}
	return nil
}

func newDataCommand(runtime runtimeResolver, stdout io.Writer) *cobra.Command {
	command := newCommand(commandMetadata{
		Use:   "data",
		Short: "Query cluster data-proxy SQL and table schema endpoints.",
		Long:  "Query data-proxy SQL and schema endpoints.",
	}, stdout)
	command.RunE = func(c *cobra.Command, _ []string) error {
		return c.Help()
	}
	command.Args = cobra.NoArgs
	command.AddCommand(
		newDataQueryCommand(runtime, stdout),
		newDataSchemaCommand(runtime, stdout),
	)
	return command
}

type dataQueryOptions struct {
	clusterID  string
	sql        string
	timeout    int
	timeoutSet bool
}

func newDataQueryCommand(runtime runtimeResolver, stdout io.Writer) *cobra.Command {
	var opts dataQueryOptions
	command := newCommand(commandMetadata{
		Use:   "query",
		Short: "Execute SQL through /data-proxy/query.",
		Long: `Execute SQL through data-proxy.

Behavior:
  --cluster-id can be provided by flag, CLINIC_CLUSTER_ID, or selected profile defaults.
  --sql is required.
  --timeout must be greater than 0 when provided.`,
	}, stdout)
	command.Args = cobra.NoArgs
	command.Flags().StringVar(&opts.clusterID, "cluster-id", "", "Cluster ID.")
	command.Flags().StringVar(&opts.sql, "sql", "", "SQL query.")
	command.Flags().IntVar(&opts.timeout, "timeout", 0, "Query timeout in seconds.")
	command.RunE = func(c *cobra.Command, _ []string) error {
		ctx, err := runtime()
		if err != nil {
			return err
		}
		opts.timeoutSet = c.Flags().Changed("timeout")
		return runDataQuery(ctx, opts, stdout)
	}
	return command
}

func runDataQuery(ctx *RuntimeContext, opts dataQueryOptions, stdout io.Writer) error {
	resolvedClusterID, err := requiredFlagOrEnvWithContext(ctx, "cluster-id", opts.clusterID, envClinicClusterID)
	if err != nil {
		return err
	}
	if opts.sql == "" {
		return errors.New("missing required --sql")
	}

	body := map[string]any{
		"clusterId": resolvedClusterID,
		"sql":       opts.sql,
	}
	if opts.timeoutSet {
		if opts.timeout <= 0 {
			return errors.New("timeout must be greater than 0")
		}
		body["timeout"] = opts.timeout
	}

	return executeClinicJSON(ctx, clinic.Request{
		Method: http.MethodPost,
		Path:   "/data-proxy/query",
		Body:   body,
	}, stdout)
}

type dataSchemaOptions struct {
	clusterID string
	tables    string
}

func newDataSchemaCommand(runtime runtimeResolver, stdout io.Writer) *cobra.Command {
	var opts dataSchemaOptions
	command := newCommand(commandMetadata{
		Use:   "schema",
		Short: "Fetch table schema through /data-proxy/schema.",
		Long: `Fetch schema for selected tables.

Behavior:
  --cluster-id can be provided by flag, CLINIC_CLUSTER_ID, or selected profile defaults.
  --tables is required and accepts comma-separated table names.`,
	}, stdout)
	command.Args = cobra.NoArgs
	command.Flags().StringVar(&opts.clusterID, "cluster-id", "", "Cluster ID.")
	command.Flags().StringVar(&opts.tables, "tables", "", "Comma-separated table list.")
	command.RunE = func(_ *cobra.Command, _ []string) error {
		ctx, err := runtime()
		if err != nil {
			return err
		}
		return runDataSchema(ctx, opts, stdout)
	}
	return command
}

func runDataSchema(ctx *RuntimeContext, opts dataSchemaOptions, stdout io.Writer) error {
	resolvedClusterID, err := requiredFlagOrEnvWithContext(ctx, "cluster-id", opts.clusterID, envClinicClusterID)
	if err != nil {
		return err
	}
	if opts.tables == "" {
		return errors.New("missing required --tables")
	}

	params := url.Values{}
	params.Set("clusterId", resolvedClusterID)
	params.Set("tables", opts.tables)

	return executeClinicJSON(ctx, clinic.Request{
		Method: http.MethodGet,
		Path:   "/data-proxy/schema",
		Query:  params,
	}, stdout)
}
