package integrationservice

import (
	"errors"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
)

const (
	HTTPAddressEnv              = "NEXUSCRM_INTEGRATION_HTTP_ADDR"
	OrgIDEnv                    = "NEXUSCRM_INTEGRATION_ORG_ID"
	AppInstallationIDEnv        = "NEXUSCRM_INTEGRATION_APP_INSTALLATION_ID"
	AuthorizedWorkloadIDEnv     = "NEXUSCRM_INTEGRATION_AUTHORIZED_WORKLOAD_ID"
	WorkloadTokenFileEnv        = "NEXUSCRM_INTEGRATION_WORKLOAD_TOKEN_FILE"
	RuntimeTargetEnv            = "NEXUSCRM_INTEGRATION_RUNTIME_TARGET"
	RuntimeCAFileEnv            = "NEXUSCRM_INTEGRATION_RUNTIME_CA_FILE"
	RuntimeServerNameEnv        = "NEXUSCRM_INTEGRATION_RUNTIME_SERVER_NAME"
	RuntimeClientCertificateEnv = "NEXUSCRM_INTEGRATION_RUNTIME_CLIENT_CERT_FILE"
	RuntimeClientKeyEnv         = "NEXUSCRM_INTEGRATION_RUNTIME_CLIENT_KEY_FILE"
	RuntimeAttemptTimeoutEnv    = "NEXUSCRM_INTEGRATION_RUNTIME_ATTEMPT_TIMEOUT"
	RuntimeStartupTimeoutEnv    = "NEXUSCRM_INTEGRATION_RUNTIME_STARTUP_TIMEOUT"
	ShutdownTimeoutEnv          = "NEXUSCRM_INTEGRATION_SHUTDOWN_TIMEOUT"
	ReadHeaderTimeoutEnv        = "NEXUSCRM_INTEGRATION_READ_HEADER_TIMEOUT"
	RequestTimeoutEnv           = "NEXUSCRM_INTEGRATION_REQUEST_TIMEOUT"
	IdleTimeoutEnv              = "NEXUSCRM_INTEGRATION_IDLE_TIMEOUT"
)

type LookupEnv func(string) (string, bool)

type Config struct {
	HTTPAddress           string
	OrgID                 string
	AppInstallationID     string
	AuthorizedWorkloadID  string
	Runtime               runtimegrpc.RuntimeClientConfig
	RuntimeStartupTimeout time.Duration
	ShutdownTimeout       time.Duration
	ReadHeaderTimeout     time.Duration
	RequestTimeout        time.Duration
	IdleTimeout           time.Duration
	ConnectedMail         *ConnectedMailConfig
}

func LoadConfig(lookup LookupEnv) (Config, error) {
	if lookup == nil {
		lookup = os.LookupEnv
	}
	required := func(name string) (string, error) {
		value, ok := lookup(name)
		if !ok || value == "" || strings.TrimSpace(value) != value {
			return "", fmt.Errorf("integration service: %s is required and must be canonical", name)
		}
		return value, nil
	}
	duration := func(name string) (time.Duration, error) {
		value, err := required(name)
		if err != nil {
			return 0, err
		}
		parsed, err := time.ParseDuration(value)
		if err != nil || parsed <= 0 {
			return 0, fmt.Errorf("integration service: %s must be a positive duration", name)
		}
		return parsed, nil
	}

	address, err := required(HTTPAddressEnv)
	if err != nil {
		return Config{}, err
	}
	_, port, err := net.SplitHostPort(address)
	if err != nil {
		return Config{}, fmt.Errorf("integration service: %s must be a host:port address: %w", HTTPAddressEnv, err)
	}
	parsedPort, err := strconv.Atoi(port)
	if err != nil || parsedPort <= 0 || parsedPort > 65535 {
		return Config{}, fmt.Errorf("integration service: %s port must be between 1 and 65535", HTTPAddressEnv)
	}
	orgID, err := required(OrgIDEnv)
	if err != nil {
		return Config{}, err
	}
	installationID, err := required(AppInstallationIDEnv)
	if err != nil {
		return Config{}, err
	}
	workloadID, err := required(AuthorizedWorkloadIDEnv)
	if err != nil {
		return Config{}, err
	}
	target, err := required(RuntimeTargetEnv)
	if err != nil {
		return Config{}, err
	}
	caFile, err := required(RuntimeCAFileEnv)
	if err != nil {
		return Config{}, err
	}
	serverName, err := required(RuntimeServerNameEnv)
	if err != nil {
		return Config{}, err
	}
	tokenFile, err := required(WorkloadTokenFileEnv)
	if err != nil {
		return Config{}, err
	}
	workloadToken, err := readWorkloadToken(tokenFile)
	if err != nil {
		return Config{}, err
	}

	runtimeAttemptTimeout, err := duration(RuntimeAttemptTimeoutEnv)
	if err != nil {
		return Config{}, err
	}
	runtimeStartupTimeout, err := duration(RuntimeStartupTimeoutEnv)
	if err != nil {
		return Config{}, err
	}
	shutdownTimeout, err := duration(ShutdownTimeoutEnv)
	if err != nil {
		return Config{}, err
	}
	readHeaderTimeout, err := duration(ReadHeaderTimeoutEnv)
	if err != nil {
		return Config{}, err
	}
	requestTimeout, err := duration(RequestTimeoutEnv)
	if err != nil {
		return Config{}, err
	}
	idleTimeout, err := duration(IdleTimeoutEnv)
	if err != nil {
		return Config{}, err
	}

	optional := func(name string) string {
		value, _ := lookup(name)
		return value
	}
	clientCertificate := optional(RuntimeClientCertificateEnv)
	clientKey := optional(RuntimeClientKeyEnv)
	if (clientCertificate == "") != (clientKey == "") {
		return Config{}, errors.New("integration service: Runtime client certificate and key must be configured together")
	}
	connectedMail, err := loadConnectedMailConfig(lookup, required)
	if err != nil {
		return Config{}, err
	}
	return Config{
		HTTPAddress: address, OrgID: orgID, AppInstallationID: installationID,
		AuthorizedWorkloadID: workloadID, RuntimeStartupTimeout: runtimeStartupTimeout,
		ShutdownTimeout: shutdownTimeout, ReadHeaderTimeout: readHeaderTimeout,
		RequestTimeout: requestTimeout, IdleTimeout: idleTimeout,
		ConnectedMail: connectedMail,
		Runtime: runtimegrpc.RuntimeClientConfig{
			Target: target, AppInstallationID: installationID, WorkloadToken: workloadToken,
			CAFile: caFile, ServerName: serverName, AttemptTimeout: runtimeAttemptTimeout,
			ClientCertificateFile: clientCertificate, ClientKeyFile: clientKey,
		},
	}, nil
}

func readWorkloadToken(path string) (string, error) {
	if !filepath.IsAbs(path) || filepath.Clean(path) != path {
		return "", errors.New("integration service: workload token file must use an absolute canonical path")
	}
	info, err := os.Stat(path)
	if err != nil {
		return "", fmt.Errorf("integration service: inspect workload token: %w", err)
	}
	if !info.Mode().IsRegular() || info.Size() == 0 || info.Mode().Perm()&0o007 != 0 {
		return "", errors.New("integration service: workload token must be a non-empty regular file unreadable by others")
	}
	raw, err := os.ReadFile(path)
	if err != nil {
		return "", fmt.Errorf("integration service: read workload token: %w", err)
	}
	token := strings.TrimSuffix(strings.TrimSuffix(string(raw), "\n"), "\r")
	if token == "" || strings.TrimSpace(token) != token || strings.ContainsAny(token, "\r\n") {
		return "", errors.New("integration service: workload token file must contain one canonical token")
	}
	return token, nil
}
