// Package agentcli implements the dify-agent CLI that runs inside the sandbox
// container. It communicates with the Agent Stub server on the host via HTTP
// or gRPC to provide connect, file, drive, and config operations.
package agentcli

import (
	"errors"
	"fmt"
	"net/url"
	"os"
	"strings"

	"github.com/langgenius/dify/dify-agent-runtime/internal/envvar"
)

const (
	EnvAPIBaseURL = envvar.EnvAgentStubAPIBaseURL
	EnvAuthJWE    = envvar.EnvAgentStubAuthJWE
	EnvDriveBase  = envvar.EnvAgentStubDriveBase

	DefaultDriveBase = envvar.DefaultDriveBase
)

// Environment holds validated Agent Stub connection parameters.
type Environment struct {
	URL     string
	AuthJWE string
}

// Endpoint represents a parsed Agent Stub endpoint with transport info.
type Endpoint struct {
	URL    string
	Scheme string // "http", "https", or "grpc"
	Host   string
	Port   string
	IsGRPC bool
}

var ErrMissingEnvironment = errors.New("missing required Agent Stub environment variables")

// ReadEnvironment reads and validates the Agent Stub env vars.
func ReadEnvironment() (*Environment, error) {
	apiURL := strings.TrimSpace(os.Getenv(EnvAPIBaseURL))
	authJWE := strings.TrimSpace(os.Getenv(EnvAuthJWE))

	var missing []string
	if apiURL == "" {
		missing = append(missing, EnvAPIBaseURL)
	}
	if authJWE == "" {
		missing = append(missing, EnvAuthJWE)
	}
	if len(missing) > 0 {
		return nil, fmt.Errorf("%w: %s", ErrMissingEnvironment, strings.Join(missing, ", "))
	}

	endpoint, err := ParseEndpoint(apiURL)
	if err != nil {
		return nil, fmt.Errorf("invalid %s: %w", EnvAPIBaseURL, err)
	}

	return &Environment{
		URL:     endpoint.URL,
		AuthJWE: authJWE,
	}, nil
}

// HasEnvironment returns whether both required env vars are set.
func HasEnvironment() bool {
	return os.Getenv(EnvAPIBaseURL) != "" && os.Getenv(EnvAuthJWE) != ""
}

// ReadDriveBase returns the configured drive base or the default.
func ReadDriveBase() string {
	if v := strings.TrimSpace(os.Getenv(EnvDriveBase)); v != "" {
		return v
	}
	return DefaultDriveBase
}

// ParseEndpoint parses an Agent Stub URL and normalizes it.
func ParseEndpoint(rawURL string) (*Endpoint, error) {
	stripped := strings.TrimSpace(rawURL)
	if stripped == "" {
		return nil, errors.New("agent stub URL must not be empty")
	}

	parsed, err := url.Parse(stripped)
	if err != nil {
		return nil, fmt.Errorf("invalid URL: %w", err)
	}

	switch parsed.Scheme {
	case "http", "https":
		return parseHTTPEndpoint(parsed)
	case "grpc":
		return parseGRPCEndpoint(parsed)
	default:
		return nil, errors.New("agent stub URL must use http, https, or grpc")
	}
}

func parseHTTPEndpoint(parsed *url.URL) (*Endpoint, error) {
	if parsed.Host == "" {
		return nil, errors.New("agent stub URL must include a host")
	}
	if parsed.RawQuery != "" || parsed.Fragment != "" {
		return nil, errors.New("agent stub URL must not include a query string or fragment")
	}
	if parsed.User != nil {
		return nil, errors.New("agent stub URL must not include user info")
	}

	path := strings.TrimRight(parsed.Path, "/")
	if path == "" || path == "/" {
		path = "/agent-stub"
	} else if path != "/agent-stub" {
		return nil, errors.New("HTTP agent stub API base URL path must be empty or /agent-stub")
	}

	normalizedURL := fmt.Sprintf("%s://%s%s", parsed.Scheme, parsed.Host, path)
	return &Endpoint{
		URL:    normalizedURL,
		Scheme: parsed.Scheme,
		Host:   parsed.Hostname(),
		Port:   parsed.Port(),
		IsGRPC: false,
	}, nil
}

func parseGRPCEndpoint(parsed *url.URL) (*Endpoint, error) {
	if parsed.Host == "" {
		return nil, errors.New("gRPC agent stub URL must include a host")
	}
	path := strings.TrimRight(parsed.Path, "/")
	if path != "" && path != "/" {
		return nil, errors.New("gRPC agent stub URL must not include a path")
	}
	port := parsed.Port()
	if port == "" {
		return nil, errors.New("gRPC agent stub URL must include an explicit port")
	}

	normalizedURL := fmt.Sprintf("grpc://%s:%s", parsed.Hostname(), port)
	return &Endpoint{
		URL:    normalizedURL,
		Scheme: "grpc",
		Host:   parsed.Hostname(),
		Port:   port,
		IsGRPC: true,
	}, nil
}
