package cli

import (
	"context"
	"encoding/json"
	"fmt"
	"regexp"

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

var (
	codexSessionIDPattern   = regexp.MustCompile(`(?mi)^session id:\s*(\S+)\s*$`)
	codexThreadStartedRegex = regexp.MustCompile(`\{[^{}]*"type"\s*:\s*"thread\.started"[^{}]*\}`)
)

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

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

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

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

	if externalID := parseCodexSessionID(output); externalID != "" {
		sess.externalID = externalID
	}
	if sess.externalID == "" {
		return fmt.Errorf("cli adapter: codex exec did not report a session id")
	}
	return nil
}

func (e *codexExecutor) resolveCodexExecSession(spec runtime.ExecutionSpec) (*codexExecSession, error) {
	target := cliTargetName(spec)
	key := sessionKey{target: target, sessionID: normalizeSessionID(spec.SessionID)}

	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 persistent session for target %q session %q", target, key.sessionID)
	}
	sess := &codexExecSession{key: key}
	e.sessions[key] = sess
	return sess, nil
}

func (e *codexExecutor) 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{"codex-exec", k.target, k.sessionID})
	}
	return snaps
}

func (e *codexExecutor) Terminate(ctx context.Context, target, sessionID string) (bool, error) {
	key := sessionKey{target: target, sessionID: normalizeSessionID(sessionID)}
	e.mu.Lock()
	_, ok := e.sessions[key]
	if ok {
		delete(e.sessions, key)
	}
	e.mu.Unlock()
	return ok, nil
}

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

func codexExecArgs(profile config.CLIProfileConf, externalSessionID, prompt string) []string {
	if externalSessionID == "" {
		args := append([]string{}, profile.Args...)
		return append(args, prompt)
	}
	args := append([]string{}, profile.ResumeArgs...)
	return append(args, externalSessionID, prompt)
}

func parseCodexSessionID(output string) string {
	if matches := codexSessionIDPattern.FindStringSubmatch(output); len(matches) >= 2 {
		return matches[1]
	}
	for _, raw := range codexThreadStartedRegex.FindAllString(output, -1) {
		var ev struct {
			Type     string `json:"type"`
			ThreadID string `json:"thread_id"`
		}
		if err := json.Unmarshal([]byte(raw), &ev); err == nil && ev.Type == "thread.started" && ev.ThreadID != "" {
			return ev.ThreadID
		}
	}
	return ""
}
