// Package build describes the unified AgentTeam server that dev mode compiles from
// source and drives its build and launch.
package build

import (
	"fmt"
	"log/slog"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

// Spec describes a service binary that dev mode builds and runs.
type Spec struct {
	Name        string
	BinRel      string
	SourceDir   string
	MainPkg     string
	ReadyEnv    string
	DefaultURL  string
	Optional    bool
	Ldflags     func() string
	BuildArgs   func(bin string) []string
	BeforeBuild func() error
	DDLAware    bool
}

// ServerBinRel is the repo-relative path where the agentteam-server binary is built.
const ServerBinRel = ".data/dev/agentteam-server"

// AgentTeamServer returns the spec for cmd/agentteam-server under repoRoot.
func AgentTeamServer(repoRoot string) Spec {
	return Spec{
		Name:       "agentteam-server",
		BinRel:     ServerBinRel,
		SourceDir:  repoRoot,
		MainPkg:    "./cmd/agentteam-server",
		ReadyEnv:   "AGENTTEAM_HTTP_ADDRESS",
		DefaultURL: ":3001",
		BuildArgs: func(bin string) []string {
			return agentteamServerBuildArgs(repoRoot, bin)
		},
	}
}

// Available reports whether the service source exists.
func (s Spec) Available() bool {
	if !s.Optional {
		return true
	}
	_, err := os.Stat(filepath.Join(s.SourceDir, strings.TrimPrefix(s.MainPkg, "./"), "main.go"))
	return err == nil
}

// Build compiles the binary to repoRoot/BinRel.
func (s Spec) Build(repoRoot string) error {
	if !s.Available() {
		slog.Info("source not found; skipping build", "service", s.Name)
		return nil
	}
	return s.buildTo(repoRoot, filepath.Join(repoRoot, s.BinRel))
}

// BuildTemp compiles to a sibling temp file and returns its path. Caller
// renames it into place after a successful restart.
func (s Spec) BuildTemp(repoRoot string) (string, error) {
	if !s.Available() {
		slog.Info("source not found; skipping build", "service", s.Name)
		return "", nil
	}
	bin := filepath.Join(repoRoot, s.BinRel)
	tmp := fmt.Sprintf("%s.tmp.%d", bin, os.Getpid())
	if err := s.buildTo(repoRoot, tmp); err != nil {
		_ = os.Remove(tmp)
		return "", err
	}
	return tmp, nil
}

func (s Spec) buildTo(repoRoot, bin string) error {
	os.MkdirAll(filepath.Dir(bin), 0755)
	if s.BeforeBuild != nil {
		if err := s.BeforeBuild(); err != nil {
			return err
		}
	}
	slog.Info("building...", "service", s.Name)
	cmd := exec.Command("go", s.defaultBuildArgs(repoRoot, bin)...)
	cmd.Dir = s.SourceDir
	cmd.Stdout = os.Stderr
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("%s build failed: %w", s.Name, err)
	}
	slog.Info("build ok", "service", s.Name)
	return nil
}

func (s Spec) defaultBuildArgs(repoRoot, bin string) []string {
	if s.BuildArgs != nil {
		return s.BuildArgs(bin)
	}
	args := []string{"build"}
	if s.Ldflags != nil {
		args = append(args, "-ldflags", s.Ldflags())
	}
	return append(args, "-o", bin, s.MainPkg)
}

func buildLdflags(repoRoot string) string {
	commit := os.Getenv("AGENTTEAM_SERVER_GIT_COMMIT")
	if commit == "" {
		cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
		cmd.Dir = repoRoot
		if out, err := cmd.Output(); err == nil {
			commit = strings.TrimSpace(string(out))
		}
	}
	if commit == "" {
		commit = "unknown"
	}
	version := strings.TrimSpace(os.Getenv("AGENTTEAM_SERVER_VERSION"))
	if version == "" {
		cmd := exec.Command("git", "describe", "--tags", "--always", "--dirty")
		cmd.Dir = repoRoot
		if out, err := cmd.Output(); err == nil {
			version = strings.TrimSpace(string(out))
		}
	}
	buildTime := strings.TrimSpace(os.Getenv("AGENTTEAM_SERVER_BUILD_TIME"))
	if buildTime == "" {
		buildTime = time.Now().UTC().Format(time.RFC3339)
	}
	var parts []string
	if version != "" {
		parts = append(parts, "-X", "github.com/tidbcloud/AgentTeam/internal/versioning.Version="+version)
	}
	parts = append(parts, "-X", "github.com/tidbcloud/AgentTeam/internal/versioning.GitCommit="+commit)
	parts = append(parts, "-X", "github.com/tidbcloud/AgentTeam/internal/versioning.BuildTime="+buildTime)
	return strings.Join(parts, " ")
}

func agentteamServerBuildArgs(repoRoot, bin string) []string {
	return []string{"build", "-buildvcs=false", "-ldflags", buildLdflags(repoRoot), "-o", bin, "./cmd/agentteam-server"}
}
