package project

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/fs"
	"path/filepath"
	"strings"

	"github.com/databricks/cli/libs/cmdio"
	"github.com/databricks/cli/libs/log"
	"github.com/databricks/cli/libs/process"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

type proxy struct {
	Entrypoint    `yaml:",inline"`
	Name          string `yaml:"name"`
	Description   string `yaml:"description"`
	TableTemplate string `yaml:"table_template,omitempty"`
	Flags         []flag `yaml:"flags,omitempty"`
}

func (cp *proxy) register(parent *cobra.Command) {
	cmd := &cobra.Command{
		Use:   cp.Name,
		Short: cp.Description,
		RunE:  cp.runE,
	}
	parent.AddCommand(cmd)
	flags := cmd.Flags()
	for _, flag := range cp.Flags {
		flag.register(flags)
	}
}

func (cp *proxy) runE(cmd *cobra.Command, _ []string) error {
	err := cp.checkUpdates(cmd)
	if err != nil {
		return err
	}
	args, err := cp.commandInput(cmd)
	if err != nil {
		return err
	}
	envs, err := cp.Prepare(cmd)
	if err != nil {
		return fmt.Errorf("entrypoint: %w", err)
	}
	ctx := cmd.Context()
	log.Debugf(ctx, "Forwarding subprocess: %s", strings.Join(args, " "))
	if cp.TableTemplate != "" {
		return cp.renderJsonAsTable(cmd, args, envs)
	}
	err = process.Forwarded(ctx, args,
		cmd.InOrStdin(),
		cmd.OutOrStdout(),
		cmd.ErrOrStderr(),
		process.WithEnvs(envs))
	if errors.Is(err, fs.ErrNotExist) && cp.IsPythonProject() {
		msg := "cannot find Python %s. Please re-run: databricks labs install %s"
		return fmt.Errorf(msg, cp.MinPython, cp.Name)
	}
	return err
}

// [EXPERIMENTAL] this interface contract may change in the future.
// See https://github.com/databricks/cli/issues/994
func (cp *proxy) renderJsonAsTable(cmd *cobra.Command, args []string, envs map[string]string) error {
	var buf bytes.Buffer
	ctx := cmd.Context()
	err := process.Forwarded(ctx, args,
		cmd.InOrStdin(),
		&buf,
		cmd.ErrOrStderr(),
		process.WithEnvs(envs))
	if err != nil {
		return err
	}
	var anyVal any
	err = json.Unmarshal(buf.Bytes(), &anyVal)
	if err != nil {
		return err
	}
	// IntelliJ eagerly replaces tabs with spaces, even though we're not asking for it
	fixedTemplate := strings.ReplaceAll(cp.TableTemplate, "\\t", "\t")
	return cmdio.RenderWithTemplate(ctx, anyVal, "", fixedTemplate)
}

func (cp *proxy) commandInput(cmd *cobra.Command) ([]string, error) {
	flags := cmd.Flags()
	commandInput := struct {
		Command    string         `json:"command"`
		Flags      map[string]any `json:"flags"`
		OutputType string         `json:"output_type"`
	}{
		Command: cp.Name,
		Flags:   map[string]any{},
	}
	for _, f := range cp.Flags {
		v, err := f.get(flags)
		if err != nil {
			return nil, fmt.Errorf("get %s flag: %w", f.Name, err)
		}
		commandInput.Flags[f.Name] = v
	}
	logLevelFlag := flags.Lookup("log-level")
	if logLevelFlag != nil {
		commandInput.Flags["log_level"] = logLevelFlag.Value.String()
	}
	args := []string{}
	ctx := cmd.Context()
	if cp.IsPythonProject() {
		args = append(args, cp.virtualEnvPython(ctx))
		libDir := cp.EffectiveLibDir()
		entrypoint := filepath.Join(libDir, cp.Main)
		args = append(args, entrypoint)
	}
	raw, err := json.Marshal(commandInput)
	if err != nil {
		return nil, fmt.Errorf("command input: %w", err)
	}
	args = append(args, string(raw))
	return args, nil
}

type flag struct {
	Name        string `yaml:"name"`
	Description string `yaml:"description"`
	Default     any    `yaml:"default,omitempty"`
}

func (f *flag) register(pf *pflag.FlagSet) {
	var dflt string
	if f.Default != nil {
		dflt = fmt.Sprint(f.Default)
	}
	pf.String(f.Name, dflt, f.Description)
}

func (f *flag) get(pf *pflag.FlagSet) (any, error) {
	return pf.GetString(f.Name)
}
