// Package team implements the team primitive — small composable units of
// ≤5 agents with identity, philosophy, goals, context, and memory. See
// plan §4.1.
//
// Mutable by the team itself: memory, internal goals decomposition,
// internal topology, internal context routing.
//
// Immutable: identity (id, philosophy, base roles), core safety policies.
//
// Memory is a markdown file at MemoryPath. Each entry is a success or
// failure record; teams append via RecordOutcome.
package team

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

	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/store"
)

// Topology is the team's internal coordination shape.
type Topology string

const (
	TopologyTopDown Topology = "top-down"
	TopologyFlat    Topology = "flat"
)

// Team is the runtime handle.
type Team struct {
	ID         string
	Name       string
	Philosophy string
	Topology   Topology
	MemoryPath string
	Roster     []string // agent IDs (≤5)
}

// Service owns the teams table and memory files.
type Service struct {
	St *store.Store
}

// New constructs a Service.
func New(st *store.Store) *Service { return &Service{St: st} }

// Create inserts a team row and writes a starter memory file. Roster must be
// at most 5 agent ids.
func (s *Service) Create(ctx context.Context, t Team) error {
	if t.ID == "" {
		return errors.New("team: id required")
	}
	if len(t.Roster) > 5 {
		return fmt.Errorf("team: roster size %d > 5 (immutable cap)", len(t.Roster))
	}
	if t.Topology == "" {
		t.Topology = TopologyTopDown
	}
	if t.MemoryPath == "" {
		return errors.New("team: memory_path required")
	}
	if err := s.ensureMemoryFile(t.MemoryPath, t.ID, t.Philosophy); err != nil {
		return err
	}
	return s.St.Tx(ctx, func(q store.Querier) error {
		_, err := q.Exec(
			`INSERT INTO teams(id, name, philosophy, topology, memory_path, status) VALUES(?, ?, ?, ?, ?, 'idle')`,
			t.ID, t.Name, nullable(t.Philosophy), string(t.Topology), t.MemoryPath,
		)
		return err
	})
}

// Get loads a team by id.
func (s *Service) Get(ctx context.Context, id string) (Team, error) {
	row := s.St.DB().QueryRowContext(ctx,
		`SELECT id, name, IFNULL(philosophy,''), IFNULL(topology,'top-down'), IFNULL(memory_path,'') FROM teams WHERE id=?`,
		id,
	)
	var t Team
	if err := row.Scan(&t.ID, &t.Name, &t.Philosophy, &t.Topology, &t.MemoryPath); err != nil {
		return Team{}, err
	}
	return t, nil
}

// RecordOutcome appends a success/failure entry to the team's memory file.
// Mutable by the team itself per the plan.
func (s *Service) RecordOutcome(ctx context.Context, teamID, kind, task, routine, notes string) error {
	t, err := s.Get(ctx, teamID)
	if err != nil {
		return err
	}
	entry := fmt.Sprintf("- ts: %s\n  kind: %s\n  task: %s\n  routine: %q\n  notes: %s\n",
		store.FmtTime(time.Now().UTC()), kind, task, routine, notes,
	)
	f, err := os.OpenFile(t.MemoryPath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o644)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = f.WriteString(entry)
	return err
}

// LoadMemory returns the team's memory file as a string. Used to seed
// context_in for the next task the team picks up.
func (s *Service) LoadMemory(ctx context.Context, teamID string) (string, error) {
	t, err := s.Get(ctx, teamID)
	if err != nil {
		return "", err
	}
	b, err := os.ReadFile(t.MemoryPath)
	if err != nil {
		return "", err
	}
	return string(b), nil
}

// SnapshotTemplate writes the team's current memory + roster as a template
// at .td/teams_templates/<teamID>-v<n>.md. Returns the template id.
func (s *Service) SnapshotTemplate(ctx context.Context, teamID, snapshotDir string) (string, error) {
	t, err := s.Get(ctx, teamID)
	if err != nil {
		return "", err
	}
	mem, _ := os.ReadFile(t.MemoryPath)
	tmplID := fmt.Sprintf("tt_%s_%d", teamID, time.Now().Unix())
	tmplPath := filepath.Join(snapshotDir, tmplID+".md")
	if err := os.MkdirAll(snapshotDir, 0o755); err != nil {
		return "", err
	}
	body := fmt.Sprintf("---\nteam: %s\nphilosophy: %s\ntopology: %s\n---\n\n# Snapshot\n\n%s\n",
		t.Name, t.Philosophy, t.Topology, string(mem))
	if err := os.WriteFile(tmplPath, []byte(body), 0o644); err != nil {
		return "", err
	}
	err = s.St.Tx(ctx, func(q store.Querier) error {
		_, err := q.Exec(
			`INSERT INTO teams_templates(id, source_team_id, snapshot_path, created_at) VALUES(?, ?, ?, ?)`,
			tmplID, teamID, tmplPath, store.FmtTime(store.Now()),
		)
		return err
	})
	return tmplID, err
}

func (s *Service) ensureMemoryFile(path, teamID, philosophy string) error {
	if _, err := os.Stat(path); err == nil {
		return nil
	}
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		return err
	}
	header := fmt.Sprintf("# Team memory: %s\n\nPhilosophy: %s\n\n## Entries\n\n",
		teamID, strings.TrimSpace(philosophy),
	)
	return os.WriteFile(path, []byte(header), 0o644)
}

func nullable(s string) any {
	if s == "" {
		return nil
	}
	return s
}
