package dispatcher

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

const (
	pendingLocalMemoryMarker = ".agentteam-pending-memory.json"
	pendingLocalMemoryIndex  = "# Memory\n"
	pendingLocalMemorySchema = 1
)

type pendingLocalMemoryState struct {
	SchemaVersion int    `json:"schemaVersion"`
	MemoryID      string `json:"memoryId"`
	AgentID       string `json:"agentId"`
	Branch        string `json:"branch"`
}

// ensurePendingLocalMemoryRoot creates the managed Memory path after
// an authoritative binding was resolved but its first clone could not be
// created. The marker is binding-scoped so a later attach imports this root
// even when legacy local-content import is disabled.
func ensurePendingLocalMemoryRoot(workDir, agentID string, access MemoryAccess) error {
	memoryDir := filepath.Join(workDir, "memory")
	if info, err := os.Stat(filepath.Join(memoryDir, ".git")); err == nil && info.IsDir() {
		return nil
	} else if err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("memory pending: stat .git: %w", err)
	}
	if err := os.MkdirAll(memoryDir, 0o755); err != nil {
		return fmt.Errorf("memory pending: mkdir root: %w", err)
	}
	markerPath := filepath.Join(memoryDir, pendingLocalMemoryMarker)
	state := pendingLocalMemoryState{
		SchemaVersion: pendingLocalMemorySchema,
		MemoryID:      strings.TrimSpace(access.MemoryID),
		AgentID:       strings.TrimSpace(agentID),
		Branch:        strings.TrimSpace(access.Branch),
	}
	data, err := json.MarshalIndent(state, "", "  ")
	if err != nil {
		return fmt.Errorf("memory pending: encode state: %w", err)
	}
	data = append(data, '\n')
	if err := ensurePendingLocalMemoryState(markerPath, data, agentID, access); err != nil {
		return fmt.Errorf("memory pending: write state: %w", err)
	}
	return ensurePendingLocalMemoryIndex(filepath.Join(memoryDir, "MEMORY.md"))
}

// ensurePendingLocalMemoryState atomically claims an unmarked root for one
// binding. The fully-written temp file is hard-linked into place so concurrent
// bindings cannot overwrite each other and readers never observe partial JSON.
func ensurePendingLocalMemoryState(markerPath string, data []byte, agentID string, access MemoryAccess) error {
	tmp, err := os.CreateTemp(filepath.Dir(markerPath), ".agentteam-pending-memory-*")
	if err != nil {
		return err
	}
	tmpPath := tmp.Name()
	defer os.Remove(tmpPath)
	if err := tmp.Chmod(0o600); err != nil {
		_ = tmp.Close()
		return err
	}
	if _, err := tmp.Write(data); err != nil {
		_ = tmp.Close()
		return err
	}
	if err := tmp.Sync(); err != nil {
		_ = tmp.Close()
		return err
	}
	if err := tmp.Close(); err != nil {
		return err
	}
	if err := os.Link(tmpPath, markerPath); err != nil {
		if !os.IsExist(err) {
			return err
		}
		matching, matchErr := matchingPendingLocalMemoryRoot(filepath.Dir(markerPath), agentID, access)
		if matchErr != nil {
			return matchErr
		}
		if !matching {
			return fmt.Errorf("existing root belongs to another binding")
		}
	}
	return nil
}

func ensurePendingLocalMemoryIndex(indexPath string) error {
	file, err := os.OpenFile(indexPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
	if err != nil {
		if os.IsExist(err) {
			return nil
		}
		return fmt.Errorf("memory pending: create index: %w", err)
	}
	if _, err := file.Write([]byte(pendingLocalMemoryIndex)); err != nil {
		_ = file.Close()
		_ = os.Remove(indexPath)
		return fmt.Errorf("memory pending: write index: %w", err)
	}
	if err := file.Close(); err != nil {
		_ = os.Remove(indexPath)
		return fmt.Errorf("memory pending: close index: %w", err)
	}
	return nil
}

func matchingPendingLocalMemoryRoot(memoryDir, agentID string, access MemoryAccess) (bool, error) {
	state, err := readPendingLocalMemoryState(memoryDir)
	if err != nil || state == nil {
		return false, err
	}
	return pendingLocalMemoryStateMatches(*state, agentID, access), nil
}

func readPendingLocalMemoryState(memoryDir string) (*pendingLocalMemoryState, error) {
	data, err := os.ReadFile(filepath.Join(memoryDir, pendingLocalMemoryMarker))
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("memory pending: read state: %w", err)
	}
	var state pendingLocalMemoryState
	if err := json.Unmarshal(data, &state); err != nil {
		return nil, fmt.Errorf("memory pending: decode state: %w", err)
	}
	if state.SchemaVersion != pendingLocalMemorySchema {
		return nil, fmt.Errorf("memory pending: unsupported state schema %d", state.SchemaVersion)
	}
	if strings.TrimSpace(state.MemoryID) == "" || strings.TrimSpace(state.AgentID) == "" || strings.TrimSpace(state.Branch) == "" {
		return nil, fmt.Errorf("memory pending: incomplete binding state")
	}
	return &state, nil
}

func pendingLocalMemoryStateMatches(state pendingLocalMemoryState, agentID string, access MemoryAccess) bool {
	return strings.TrimSpace(state.MemoryID) == strings.TrimSpace(access.MemoryID) &&
		strings.TrimSpace(state.AgentID) == strings.TrimSpace(agentID) &&
		strings.TrimSpace(state.Branch) == strings.TrimSpace(access.Branch)
}
