package cli

import (
	"context"
	"fmt"
	"os"
	"regexp"

	"iop/apps/node/internal/runtime"
	"iop/packages/go/config"
)

var antigravityConversationPatterns = []*regexp.Regexp{
	regexp.MustCompile(`(?m)Created conversation (` + uuidPattern + `)`),
	regexp.MustCompile(`(?m)Streaming conversation (` + uuidPattern + `)`),
	regexp.MustCompile(`(?m)Print mode: conversation=(` + uuidPattern + `)`),
	regexp.MustCompile(`(?m)conversationID="(` + uuidPattern + `)"`),
}

const uuidPattern = `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`

func (e *antigravityExecutor) Execute(ctx context.Context, spec runtime.ExecutionSpec, profile config.CLIProfileConf, sink runtime.EventSink) error {
	if len(profile.ResumeArgs) == 0 {
		return fmt.Errorf("cli adapter: antigravity-print mode requires resume_args in profile %q", spec.Target)
	}

	sess, err := e.resolveAntigravitySession(spec)
	if err != nil {
		return err
	}

	sess.mu.Lock()
	defer sess.mu.Unlock()

	logFile, cleanup, err := createAntigravityLogFile()
	if err != nil {
		return err
	}
	defer cleanup()

	prompt := extractPrompt(spec.Input)
	args := antigravityPrintArgs(profile, sess.conversationID, logFile, prompt)
	output, err := e.cli.executeCommand(ctx, spec, profile, args, prompt, sink)
	if err != nil {
		return err
	}

	logOutput, readErr := os.ReadFile(logFile)
	if readErr != nil {
		return fmt.Errorf("cli adapter: read antigravity log: %w", readErr)
	}
	if conversationID := parseAntigravityConversationID(string(logOutput) + output); conversationID != "" {
		sess.conversationID = conversationID
	}
	if sess.conversationID == "" {
		return fmt.Errorf("cli adapter: antigravity-print did not report a conversation id")
	}
	return nil
}

func (e *antigravityExecutor) resolveAntigravitySession(spec runtime.ExecutionSpec) (*antigravitySession, error) {
	target := cliTargetName(spec)
	key := newSessionKey(spec)

	e.mu.Lock()
	defer e.mu.Unlock()

	if sess, ok := e.sessions[key]; ok {
		return sess, nil
	}
	if spec.SessionMode == runtime.SessionModeRequireExisting {
		return nil, fmt.Errorf("cli adapter: no antigravity conversation for target %q session %q", target, key.sessionID)
	}
	sess := &antigravitySession{key: key}
	e.sessions[key] = sess
	return sess, nil
}

func (e *antigravityExecutor) Sessions() []sessionListEntry {
	e.mu.Lock()
	defer e.mu.Unlock()
	snaps := make([]sessionListEntry, 0, len(e.sessions))
	for k := range e.sessions {
		snaps = append(snaps, sessionListEntry{"antigravity-print", k.target, k.sessionID, k.workspace})
	}
	return snaps
}

// Terminate removes every workspace variant of the given target/sessionID.
func (e *antigravityExecutor) Terminate(_ context.Context, target, sessionID string) (bool, error) {
	sid := normalizeSessionID(sessionID)
	e.mu.Lock()
	defer e.mu.Unlock()
	removed := false
	for k := range e.sessions {
		if k.target == target && k.sessionID == sid {
			delete(e.sessions, k)
			removed = true
		}
	}
	return removed, nil
}

func (e *antigravityExecutor) Stop(ctx context.Context) error {
	e.mu.Lock()
	e.sessions = make(map[sessionKey]*antigravitySession)
	e.mu.Unlock()
	return nil
}

func antigravityPrintArgs(profile config.CLIProfileConf, conversationID, logFile, prompt string) []string {
	args := []string{"--log-file", logFile}
	if conversationID == "" {
		args = append(args, profile.Args...)
	} else {
		args = append(args, removeAntigravityPrintFlag(profile.ResumeArgs)...)
		args = append(args, conversationID)
		args = append(args, "--print")
	}
	return append(args, prompt)
}

func removeAntigravityPrintFlag(args []string) []string {
	filtered := make([]string, 0, len(args))
	for _, arg := range args {
		switch arg {
		case "--print", "--prompt", "-p":
			continue
		default:
			filtered = append(filtered, arg)
		}
	}
	return filtered
}

func createAntigravityLogFile() (string, func(), error) {
	f, err := os.CreateTemp("", "iop-antigravity-*.log")
	if err != nil {
		return "", nil, fmt.Errorf("cli adapter: create antigravity log file: %w", err)
	}
	path := f.Name()
	if err := f.Close(); err != nil {
		_ = os.Remove(path)
		return "", nil, fmt.Errorf("cli adapter: close antigravity log file: %w", err)
	}
	return path, func() { _ = os.Remove(path) }, nil
}

func parseAntigravityConversationID(s string) string {
	var id string
	for _, re := range antigravityConversationPatterns {
		for _, match := range re.FindAllStringSubmatch(s, -1) {
			if len(match) >= 2 {
				id = match[1]
			}
		}
	}
	return id
}
