package orchestrator

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"

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

// Draft is the structured representation of an action.draft.
type Draft struct {
	ID       string
	TaskID   string
	AgentID  string
	Kind     string         // e.g. "code_edit", "terminate_team", "modify_org"
	Payload  map[string]any // structured action description
	PathOnDisk string       // where the payload is saved (for audit)
	Status   string
	SignedBy string
}

// Signer is the interface implementations sign-off on drafts. Policy-based
// signers run cheap deterministic checks; specialist signers ask another
// agent to inspect. See plan §5.2.
type Signer interface {
	Sign(ctx context.Context, d Draft) (approved bool, reason string, err error)
}

// PolicySigner is the cheap signer. Rules:
//   - code_edit: approve if the payload's `path` matches the agent's zone
//     scope (already enforced by the write tool; this is a redundancy guard)
//   - modify_org: reject (always requires specialist signer; Phase J)
//   - everything else: approve
type PolicySigner struct{}

func (PolicySigner) Sign(ctx context.Context, d Draft) (bool, string, error) {
	switch d.Kind {
	case "modify_org":
		return false, "policy_signer: org modifications require specialist sign-off", nil
	case "code_edit":
		path, _ := d.Payload["path"].(string)
		zone, _ := d.Payload["zone"].(string)
		if path == "" || zone == "" {
			return false, "policy_signer: code_edit requires path and zone", nil
		}
		return true, "policy_signer: in-zone code edit", nil
	}
	return true, "policy_signer: default-approve", nil
}

// SubmitDraft persists an action.draft and writes its payload to a file.
func (o *Orchestrator) SubmitDraft(ctx context.Context, d Draft, dir string) (string, error) {
	if d.ID == "" {
		d.ID = fmt.Sprintf("draft_%d", store.Now().UnixNano())
	}
	if d.Kind == "" {
		return "", errors.New("orchestrator: draft.kind required")
	}
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return "", err
	}
	path := filepath.Join(dir, d.ID+".json")
	bj, err := json.MarshalIndent(d.Payload, "", "  ")
	if err != nil {
		return "", err
	}
	if err := os.WriteFile(path, bj, 0o644); err != nil {
		return "", err
	}
	d.PathOnDisk = path

	err = o.St.Tx(ctx, func(q store.Querier) error {
		_, err := q.Exec(
			`INSERT INTO action_drafts(id, task_id, agent_id, kind, payload_path, status, created_at)
			 VALUES(?, ?, ?, ?, ?, 'pending', ?)`,
			d.ID, nullable(d.TaskID), nullable(d.AgentID), d.Kind, path, store.FmtTime(store.Now()),
		)
		return err
	})
	if err != nil {
		return "", err
	}
	_, _ = o.Bus.Emit(ctx, event.Event{
		Kind: "action.draft.created", RunID: "", TaskID: d.TaskID, AgentID: d.AgentID,
		Payload: map[string]any{"id": d.ID, "kind": d.Kind, "path": path},
	})
	return d.ID, nil
}

// SignDraft runs the signer and either approves or rejects the draft.
func (o *Orchestrator) SignDraft(ctx context.Context, draftID string, signer Signer) (bool, string, error) {
	// Load draft.
	row := o.St.DB().QueryRowContext(ctx,
		`SELECT id, IFNULL(task_id,''), IFNULL(agent_id,''), kind, payload_path FROM action_drafts WHERE id=? AND status='pending'`,
		draftID,
	)
	var d Draft
	if err := row.Scan(&d.ID, &d.TaskID, &d.AgentID, &d.Kind, &d.PathOnDisk); err != nil {
		return false, "", err
	}
	// Load payload from disk.
	raw, err := os.ReadFile(d.PathOnDisk)
	if err != nil {
		return false, "", err
	}
	_ = json.Unmarshal(raw, &d.Payload)

	approved, reason, err := signer.Sign(ctx, d)
	if err != nil {
		return false, "", err
	}
	status := "rejected"
	if approved {
		status = "approved"
	}
	updErr := o.St.Tx(ctx, func(q store.Querier) error {
		_, e := q.Exec(`UPDATE action_drafts SET status=?, signed_by=?, signed_at=? WHERE id=?`,
			status, "policy_signer", store.FmtTime(store.Now()), d.ID)
		return e
	})
	if updErr != nil {
		return false, "", updErr
	}
	kind := event.Kind("action.draft.approved")
	if !approved {
		kind = event.Kind("action.draft.rejected")
	}
	_, _ = o.Bus.Emit(ctx, event.Event{
		Kind: kind, TaskID: d.TaskID, AgentID: d.AgentID,
		Payload: map[string]any{"id": d.ID, "reason": reason},
	})
	return approved, reason, nil
}
