package cli

import (
	"context"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/unkn0wn-root/resterm/internal/grpcclient"
	"github.com/unkn0wn-root/resterm/internal/httpclient"
	"github.com/unkn0wn-root/resterm/internal/runx/check"
	"github.com/unkn0wn-root/resterm/internal/telemetry"
	str "github.com/unkn0wn-root/resterm/internal/util"
	"github.com/unkn0wn-root/resterm/internal/vars"
)

type ExecFlags struct {
	EnvName           string
	EnvFile           string
	Workspace         string
	Timeout           time.Duration
	Insecure          bool
	Follow            bool
	ProxyURL          string
	Recursive         bool
	CompareTargetsRaw string
	CompareBaseline   string

	telemetry telemetry.Config
}

type ExecConfig struct {
	FilePath       string
	Workspace      string
	Recursive      bool
	EnvSet         vars.EnvironmentSet
	EnvName        string
	EnvFile        string
	EnvFallback    string
	HTTPOpts       httpclient.Options
	GRPCOpts       grpcclient.Options
	CompareTargets []string
	CompareBase    string
}

func NewExecFlags() ExecFlags {
	tc := telemetry.ConfigFromEnv(os.Getenv)
	return ExecFlags{
		Timeout:   30 * time.Second,
		Follow:    true,
		telemetry: tc,
	}
}

func (f *ExecFlags) Bind(fs *flag.FlagSet) {
	if fs == nil || f == nil {
		return
	}
	StringVarAliases(fs, &f.EnvName, "", "Environment name to use", "env", "e")
	StringVarAliases(fs, &f.EnvFile, "", "Path to environment file", "env-file", "E")
	StringVarAliases(
		fs,
		&f.Workspace,
		"",
		"Workspace directory to scan for request files",
		"workspace",
		"w",
	)
	DurationVarAliases(fs, &f.Timeout, f.Timeout, "Request timeout", "timeout", "t")
	BoolVarAliases(fs, &f.Insecure, false, "Skip TLS certificate verification", "insecure", "k")
	BoolVarAliases(fs, &f.Follow, f.Follow, "Follow redirects", "follow", "L")
	StringVarAliases(fs, &f.ProxyURL, "", "HTTP proxy URL", "proxy", "x")
	BoolVarAliases(
		fs,
		&f.Recursive,
		false,
		"Recursively scan workspace for request files",
		"recursive",
		"R",
	)
	f.BindTelemetryFlags(fs)
	StringVarAliases(
		fs,
		&f.CompareTargetsRaw,
		"",
		"Default environments for manual compare runs (comma/space separated)",
		"compare",
		"C",
	)
	StringVarAliases(
		fs,
		&f.CompareBaseline,
		"",
		"Baseline environment when --compare is used (defaults to first target)",
		"compare-base",
		"B",
	)
}

func (f *ExecFlags) BindTelemetryFlags(fs *flag.FlagSet) {
	if fs == nil || f == nil {
		return
	}
	StringVarAliases(
		fs,
		&f.telemetry.Endpoint,
		f.telemetry.Endpoint,
		"OTLP collector endpoint used when @trace is enabled",
		"trace-otel-endpoint",
		"toe",
	)
	BoolVarAliases(
		fs,
		&f.telemetry.Insecure,
		f.telemetry.Insecure,
		"Disable TLS for OTLP trace export",
		"trace-otel-insecure",
		"toi",
	)
	StringVarAliases(
		fs,
		&f.telemetry.ServiceName,
		f.telemetry.ServiceName,
		"Override service.name resource attribute for exported spans",
		"trace-otel-service",
		"tos",
	)
}

func (f ExecFlags) ValidateEnvFlag() error {
	return runcheck.ValidateConcreteEnvironment(f.EnvName, "--env")
}

func (f ExecFlags) TelemetryConfig(version string) telemetry.Config {
	cfg := f.telemetry.Clone()
	cfg.Version = version
	return cfg
}

func (f ExecFlags) Resolve(filePath string) (ExecConfig, error) {
	if err := f.ValidateEnvFlag(); err != nil {
		return ExecConfig{}, err
	}
	filePath = CleanExecPath(filePath)
	work := resolveWorkspace(filePath, f.Workspace)

	envSet, envFile := LoadEnvironment(f.EnvFile, filePath, work)
	envName := f.EnvName
	envFallback := ""
	if envName == "" && len(envSet) > 0 {
		name := vars.DefaultEnvironment(envSet)
		if name != "" {
			envName = name
			if len(envSet) > 1 {
				envFallback = name
			}
		}
	}

	targets, err := ParseCompareTargets(f.CompareTargetsRaw)
	if err != nil {
		return ExecConfig{}, fmt.Errorf("invalid --compare value: %w", err)
	}
	base := f.CompareBaseline
	if err := runcheck.ValidateConcreteEnvironment(base, "--compare-base"); err != nil {
		return ExecConfig{}, fmt.Errorf("invalid --compare-base value: %w", err)
	}

	httpOpts := httpclient.Options{
		Timeout:            f.Timeout,
		FollowRedirects:    f.Follow,
		InsecureSkipVerify: f.Insecure,
		ProxyURL:           f.ProxyURL,
	}
	if filePath != "" {
		httpOpts.BaseDir = filepath.Dir(filePath)
	}

	return ExecConfig{
		FilePath:    filePath,
		Workspace:   work,
		Recursive:   f.Recursive,
		EnvSet:      envSet,
		EnvName:     envName,
		EnvFile:     envFile,
		EnvFallback: envFallback,
		HTTPOpts:    httpOpts,
		GRPCOpts: grpcclient.Options{
			DefaultPlaintext:    true,
			DefaultPlaintextSet: true,
		},
		CompareTargets: targets,
		CompareBase:    base,
	}, nil
}

func CleanExecPath(path string) string {
	path = str.Trim(path)
	if path == "" {
		return ""
	}
	return filepath.Clean(path)
}

func resolveWorkspace(filePath, workspace string) string {
	workspace = str.Trim(workspace)
	if workspace == "" {
		if filePath != "" {
			return filepath.Dir(filePath)
		}
		if wd, err := os.Getwd(); err == nil {
			return wd
		}
		return "."
	}
	if abs, err := filepath.Abs(workspace); err == nil {
		return abs
	}
	return workspace
}

func NewExecClient(version string, f ExecFlags) (*httpclient.Client, func() error, error) {
	provider, err := telemetry.New(f.TelemetryConfig(version))
	if err != nil {
		return httpclient.NewClientWithOptions(), nil, err
	}
	client := httpclient.NewClientWithOptions(httpclient.WithTelemetry(provider))
	return client, func() error {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		return provider.Shutdown(ctx)
	}, nil
}
