package app

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"
)

type Config struct {
	Addr                         string
	LarkAppID                    string
	LarkAppSecret                string
	LarkBotOpenID                string
	AllowedChatIDs               map[string]struct{}
	AllowedSenderOpenIDs         map[string]struct{}
	PRSenderOpenIDs              map[string]struct{}
	RepoPath                     string
	WorkspaceRoot                string
	StatePath                    string
	CodexBin                     string
	CodexHome                    string
	CodexModel                   string
	AgentEnvAllowlist            []string
	GitHubBin                    string
	GitHubRepo                   string
	AdminToken                   string
	JobTimeout                   time.Duration
	SingleTicketTimeout          time.Duration
	DeploymentTimeout            time.Duration
	DeploymentPollInterval       time.Duration
	AnalysisBaseURL              string
	AutoMergeEnabled             bool
	DeploymentWorkflow           string
	AutoTriggerEnabled           bool
	FeedbackPollInterval         time.Duration
	FeedbackBatchSize            int
	NotifyChatID                 string
	FeedbackReminderEnabled      bool
	FeedbackReminderChatID       string
	FeedbackReminderLocation     *time.Location
	FeedbackReminderHour         int
	FeedbackReminderLookbackDays int
	FeedbackReminderCoreUserURL  string
	JiraBaseURL                  string
	JiraUsername                 string
	JiraAPIToken                 string
}

func LoadConfig() (Config, error) {
	timeout, err := durationEnv("AUTO_ONCALL_OPTIMIZER_JOB_TIMEOUT", 45*time.Minute)
	if err != nil {
		return Config{}, err
	}
	singleTicketTimeout, err := durationEnv("AUTO_ONCALL_OPTIMIZER_SINGLE_TICKET_TIMEOUT", 3*time.Hour)
	if err != nil {
		return Config{}, err
	}
	deploymentTimeout, err := durationEnv("AUTO_ONCALL_OPTIMIZER_DEPLOYMENT_TIMEOUT", 60*time.Minute)
	if err != nil {
		return Config{}, err
	}
	deploymentPollInterval, err := durationEnv("AUTO_ONCALL_OPTIMIZER_DEPLOYMENT_POLL_INTERVAL", 15*time.Second)
	if err != nil {
		return Config{}, err
	}
	feedbackPollInterval, err := durationEnv("AUTO_ONCALL_OPTIMIZER_FEEDBACK_POLL_INTERVAL", time.Minute)
	if err != nil {
		return Config{}, err
	}
	autoTriggerEnabled, err := boolEnv("AUTO_ONCALL_OPTIMIZER_AUTO_TRIGGER_ENABLED", false)
	if err != nil {
		return Config{}, err
	}
	autoMergeEnabled, err := boolEnv("AUTO_ONCALL_OPTIMIZER_AUTO_MERGE_ENABLED", false)
	if err != nil {
		return Config{}, err
	}
	feedbackBatchSize, err := intEnv("AUTO_ONCALL_OPTIMIZER_FEEDBACK_BATCH_SIZE", 10)
	if err != nil {
		return Config{}, err
	}
	feedbackReminderEnabled, err := boolEnv("AUTO_ONCALL_OPTIMIZER_FEEDBACK_REMINDER_ENABLED", false)
	if err != nil {
		return Config{}, err
	}
	feedbackReminderHour, err := intEnv("AUTO_ONCALL_OPTIMIZER_FEEDBACK_REMINDER_HOUR", 17)
	if err != nil {
		return Config{}, err
	}
	feedbackReminderLookbackDays, err := intEnv("AUTO_ONCALL_OPTIMIZER_FEEDBACK_REMINDER_LOOKBACK_DAYS", 14)
	if err != nil {
		return Config{}, err
	}
	feedbackReminderLocationName := envOr("AUTO_ONCALL_OPTIMIZER_FEEDBACK_REMINDER_TIME_ZONE", "Asia/Shanghai")
	feedbackReminderLocation, err := time.LoadLocation(feedbackReminderLocationName)
	if err != nil {
		return Config{}, fmt.Errorf("load AUTO_ONCALL_OPTIMIZER_FEEDBACK_REMINDER_TIME_ZONE: %w", err)
	}
	cfg := Config{
		Addr:                         envOr("AUTO_ONCALL_OPTIMIZER_ADDR", ":8080"),
		LarkAppID:                    strings.TrimSpace(os.Getenv("AUTO_ONCALL_OPTIMIZER_LARK_APP_ID")),
		LarkAppSecret:                strings.TrimSpace(os.Getenv("AUTO_ONCALL_OPTIMIZER_LARK_APP_SECRET")),
		LarkBotOpenID:                strings.TrimSpace(os.Getenv("AUTO_ONCALL_OPTIMIZER_LARK_BOT_OPEN_ID")),
		AllowedChatIDs:               setEnv("AUTO_ONCALL_OPTIMIZER_ALLOWED_CHAT_IDS"),
		AllowedSenderOpenIDs:         setEnv("AUTO_ONCALL_OPTIMIZER_ALLOWED_SENDER_OPEN_IDS"),
		PRSenderOpenIDs:              setEnv("AUTO_ONCALL_OPTIMIZER_PR_SENDER_OPEN_IDS"),
		RepoPath:                     envOr("AUTO_ONCALL_OPTIMIZER_REPO_PATH", "/workspace/aisupport"),
		WorkspaceRoot:                envOr("AUTO_ONCALL_OPTIMIZER_WORKSPACE_ROOT", "/workspace/jobs"),
		StatePath:                    envOr("AUTO_ONCALL_OPTIMIZER_STATE_PATH", "/state/jobs.json"),
		CodexBin:                     envOr("AUTO_ONCALL_OPTIMIZER_CODEX_BIN", "codex"),
		CodexHome:                    strings.TrimSpace(os.Getenv("CODEX_HOME")),
		CodexModel:                   strings.TrimSpace(os.Getenv("AUTO_ONCALL_OPTIMIZER_CODEX_MODEL")),
		AgentEnvAllowlist:            listEnv("AUTO_ONCALL_OPTIMIZER_AGENT_ENV_ALLOWLIST"),
		GitHubBin:                    envOr("AUTO_ONCALL_OPTIMIZER_GH_BIN", "gh"),
		GitHubRepo:                   envOr("AUTO_ONCALL_OPTIMIZER_GITHUB_REPO", "tidbcloud/aisupport"),
		AdminToken:                   strings.TrimSpace(os.Getenv("AUTO_ONCALL_OPTIMIZER_ADMIN_TOKEN")),
		JobTimeout:                   timeout,
		SingleTicketTimeout:          singleTicketTimeout,
		DeploymentTimeout:            deploymentTimeout,
		DeploymentPollInterval:       deploymentPollInterval,
		AnalysisBaseURL:              strings.TrimRight(envOr("AUTO_ONCALL_OPTIMIZER_ANALYSIS_BASE_URL", "http://auto-oncall-services"), "/"),
		AutoMergeEnabled:             autoMergeEnabled,
		DeploymentWorkflow:           envOr("AUTO_ONCALL_OPTIMIZER_DEPLOYMENT_WORKFLOW", "deploy-auto-oncall-services-agent9-staging.yml"),
		AutoTriggerEnabled:           autoTriggerEnabled,
		FeedbackPollInterval:         feedbackPollInterval,
		FeedbackBatchSize:            feedbackBatchSize,
		NotifyChatID:                 strings.TrimSpace(os.Getenv("AUTO_ONCALL_OPTIMIZER_NOTIFY_CHAT_ID")),
		FeedbackReminderEnabled:      feedbackReminderEnabled,
		FeedbackReminderChatID:       strings.TrimSpace(os.Getenv("AUTO_ONCALL_OPTIMIZER_FEEDBACK_REMINDER_CHAT_ID")),
		FeedbackReminderLocation:     feedbackReminderLocation,
		FeedbackReminderHour:         feedbackReminderHour,
		FeedbackReminderLookbackDays: feedbackReminderLookbackDays,
		FeedbackReminderCoreUserURL:  strings.TrimRight(envOr("AUTO_ONCALL_OPTIMIZER_CORE_USER_BASE_URL", "http://core-server.core-server-staging.svc.cluster.local"), "/"),
		JiraBaseURL:                  strings.TrimRight(envOr("JIRA_BASE_URL", "https://tidb.atlassian.net"), "/"),
		JiraUsername:                 strings.TrimSpace(os.Getenv("JIRA_USERNAME")),
		JiraAPIToken:                 strings.TrimSpace(os.Getenv("JIRA_API_TOKEN")),
	}
	for name, value := range map[string]string{
		"AUTO_ONCALL_OPTIMIZER_LARK_APP_ID":      cfg.LarkAppID,
		"AUTO_ONCALL_OPTIMIZER_LARK_APP_SECRET":  cfg.LarkAppSecret,
		"AUTO_ONCALL_OPTIMIZER_LARK_BOT_OPEN_ID": cfg.LarkBotOpenID,
		"AUTO_ONCALL_OPTIMIZER_ADMIN_TOKEN":      cfg.AdminToken,
		"CODEX_HOME":                             cfg.CodexHome,
		"GH_TOKEN":                               strings.TrimSpace(os.Getenv("GH_TOKEN")),
	} {
		if value == "" {
			return Config{}, fmt.Errorf("%s is required", name)
		}
	}
	if len(cfg.AllowedChatIDs) == 0 || len(cfg.AllowedSenderOpenIDs) == 0 || len(cfg.PRSenderOpenIDs) == 0 {
		return Config{}, fmt.Errorf("allowed chat, sender, and PR sender lists must all be configured")
	}
	for sender := range cfg.PRSenderOpenIDs {
		if _, ok := cfg.AllowedSenderOpenIDs[sender]; !ok {
			return Config{}, fmt.Errorf("PR sender %s must also be an allowed sender", sender)
		}
	}
	if cfg.FeedbackPollInterval <= 0 || cfg.FeedbackBatchSize < 1 || cfg.FeedbackBatchSize > 100 ||
		cfg.DeploymentTimeout <= 0 || cfg.DeploymentPollInterval <= 0 {
		return Config{}, fmt.Errorf("feedback poll interval and batch size must be positive and batch size at most 100")
	}
	if cfg.AutoTriggerEnabled {
		if cfg.NotifyChatID == "" {
			return Config{}, fmt.Errorf("AUTO_ONCALL_OPTIMIZER_NOTIFY_CHAT_ID is required when automatic trigger is enabled")
		}
	}
	if cfg.FeedbackReminderHour < 0 || cfg.FeedbackReminderHour > 23 || cfg.FeedbackReminderLookbackDays < 1 || cfg.FeedbackReminderLookbackDays > 90 {
		return Config{}, fmt.Errorf("feedback reminder hour must be 0-23 and lookback days must be 1-90")
	}
	if cfg.FeedbackReminderEnabled {
		for name, value := range map[string]string{
			"AUTO_ONCALL_OPTIMIZER_FEEDBACK_REMINDER_CHAT_ID": cfg.FeedbackReminderChatID,
			"JIRA_USERNAME":  cfg.JiraUsername,
			"JIRA_API_TOKEN": cfg.JiraAPIToken,
		} {
			if value == "" {
				return Config{}, fmt.Errorf("%s is required when feedback reminder is enabled", name)
			}
		}
		if _, ok := cfg.AllowedChatIDs[cfg.FeedbackReminderChatID]; !ok {
			return Config{}, fmt.Errorf("feedback reminder chat must be included in AUTO_ONCALL_OPTIMIZER_ALLOWED_CHAT_IDS")
		}
	}
	return cfg, nil
}

func envOr(name, fallback string) string {
	if value := strings.TrimSpace(os.Getenv(name)); value != "" {
		return value
	}
	return fallback
}

func setEnv(name string) map[string]struct{} {
	result := map[string]struct{}{}
	for _, value := range strings.Split(os.Getenv(name), ",") {
		if value = strings.TrimSpace(value); value != "" {
			result[value] = struct{}{}
		}
	}
	return result
}

func listEnv(name string) []string {
	result := make([]string, 0)
	for value := range setEnv(name) {
		result = append(result, value)
	}
	return result
}

func durationEnv(name string, fallback time.Duration) (time.Duration, error) {
	value := strings.TrimSpace(os.Getenv(name))
	if value == "" {
		return fallback, nil
	}
	if seconds, err := strconv.Atoi(value); err == nil {
		return time.Duration(seconds) * time.Second, nil
	}
	parsed, err := time.ParseDuration(value)
	if err != nil {
		return 0, fmt.Errorf("parse %s: %w", name, err)
	}
	return parsed, nil
}

func boolEnv(name string, fallback bool) (bool, error) {
	value := strings.TrimSpace(os.Getenv(name))
	if value == "" {
		return fallback, nil
	}
	parsed, err := strconv.ParseBool(value)
	if err != nil {
		return false, fmt.Errorf("parse %s: %w", name, err)
	}
	return parsed, nil
}

func intEnv(name string, fallback int) (int, error) {
	value := strings.TrimSpace(os.Getenv(name))
	if value == "" {
		return fallback, nil
	}
	parsed, err := strconv.Atoi(value)
	if err != nil {
		return 0, fmt.Errorf("parse %s: %w", name, err)
	}
	return parsed, nil
}
