package buildconfig

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"gopkg.in/yaml.v3"
)

// Filename is the project-root file name. Filename TBD per design doc; using arctl.yaml.
const Filename = "arctl.yaml"

// Config is the per-project build-config. Future fields are additive; older
// arctl ignores unknown keys.
type Config struct {
	Framework     string `yaml:"framework"`
	Language      string `yaml:"language"`
	ModelProvider string `yaml:"modelProvider,omitempty"`
	ModelName     string `yaml:"modelName,omitempty"`
	// Port is set for MCPServer projects to expose HTTP transport. arctl run
	// maps it to the host and the MCP runtime binds 0.0.0.0:<port>.
	Port int `yaml:"port,omitempty"`
}

// Path returns the canonical arctl.yaml path under projectDir.
func Path(projectDir string) string {
	return filepath.Join(projectDir, Filename)
}

// Read parses arctl.yaml from projectDir.
func Read(projectDir string) (*Config, error) {
	data, err := os.ReadFile(Path(projectDir))
	if err != nil {
		return nil, fmt.Errorf("read arctl.yaml: %w", err)
	}
	var c Config
	if err := yaml.Unmarshal(data, &c); err != nil {
		return nil, fmt.Errorf("parse arctl.yaml: %w", err)
	}
	if c.Framework == "" {
		return nil, fmt.Errorf("arctl.yaml: framework is required")
	}
	if c.Language == "" {
		return nil, fmt.Errorf("arctl.yaml: language is required")
	}
	return &c, nil
}

// Write serializes cfg to arctl.yaml in projectDir, overwriting any existing file.
func Write(projectDir string, cfg *Config) error {
	if err := os.MkdirAll(projectDir, 0755); err != nil {
		return fmt.Errorf("mkdir project: %w", err)
	}
	data, err := yaml.Marshal(cfg)
	if err != nil {
		return fmt.Errorf("marshal arctl.yaml: %w", err)
	}
	return os.WriteFile(Path(projectDir), data, 0644)
}

// WriteDotEnv writes a `.env` file under projectDir listing required and
// optional env vars (sourced from the plugin's EnvSpec) with empty values
// ready for the user to fill in. If both lists are empty no file is written.
//
// Callers should also ensure `.env` is gitignored (see EnsureGitignored)
// so secrets aren't committed by accident.
func WriteDotEnv(projectDir string, required, optional []string) error {
	if len(required) == 0 && len(optional) == 0 {
		return nil
	}
	var b strings.Builder
	b.WriteString("# Generated by arctl init. Fill in values below.\n")
	if len(required) > 0 {
		b.WriteString("\n# Required\n")
		for _, k := range required {
			b.WriteString(fmt.Sprintf("%s=\n", k))
		}
	}
	if len(optional) > 0 {
		b.WriteString("\n# Optional\n")
		for _, k := range optional {
			b.WriteString(fmt.Sprintf("# %s=\n", k))
		}
	}
	if err := os.MkdirAll(projectDir, 0755); err != nil {
		return err
	}
	return os.WriteFile(filepath.Join(projectDir, ".env"), []byte(b.String()), 0644)
}

// EnsureGitignored appends `entry` to <projectDir>/.gitignore if not already present.
// Creates the file if missing. No-op if entry is already in the file.
func EnsureGitignored(projectDir, entry string) error {
	path := filepath.Join(projectDir, ".gitignore")
	var existing string
	if data, err := os.ReadFile(path); err == nil {
		existing = string(data)
	} else if !errors.Is(err, os.ErrNotExist) {
		return err
	}
	for line := range strings.SplitSeq(existing, "\n") {
		if strings.TrimSpace(line) == entry {
			return nil
		}
	}
	if existing != "" && !strings.HasSuffix(existing, "\n") {
		existing += "\n"
	}
	existing += entry + "\n"
	return os.WriteFile(path, []byte(existing), 0644)
}
