package cli

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/spf13/cobra"

	"github.com/aoagents/agent-orchestrator/backend/internal/config"
	"github.com/aoagents/agent-orchestrator/backend/internal/daemonmeta"
	"github.com/aoagents/agent-orchestrator/backend/internal/runfile"
)

const probeTimeout = 2 * time.Second

type statusOptions struct {
	json bool
}

type daemonState string

const (
	stateReady     daemonState = "ready"
	stateStopped   daemonState = "stopped"
	stateStale     daemonState = "stale"
	stateUnhealthy daemonState = "unhealthy"
	stateNotReady  daemonState = "not_ready"
)

type daemonStatus struct {
	State     daemonState `json:"state"`
	PID       int         `json:"pid,omitempty"`
	Port      int         `json:"port,omitempty"`
	StartedAt *time.Time  `json:"startedAt,omitempty"`
	Uptime    string      `json:"uptime,omitempty"`
	RunFile   string      `json:"runFile"`
	DataDir   string      `json:"dataDir"`
	Health    string      `json:"health,omitempty"`
	Ready     string      `json:"ready,omitempty"`
	Error     string      `json:"error,omitempty"`
	owned     bool
}

type probeResult struct {
	Status  string `json:"status"`
	Service string `json:"service"`
	PID     int    `json:"pid"`
}

func newStatusCommand(ctx *commandContext) *cobra.Command {
	var opts statusOptions
	cmd := &cobra.Command{
		Use:   "status",
		Short: "Show AO daemon status",
		Args:  noArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			st, err := ctx.inspectDaemon(cmd.Context())
			if err != nil {
				return err
			}
			if opts.json {
				return writeJSON(cmd.OutOrStdout(), st)
			}
			return writeStatus(cmd, st)
		},
	}
	cmd.Flags().BoolVar(&opts.json, "json", false, "Output status as JSON")
	return cmd
}

func (c *commandContext) inspectDaemon(ctx context.Context) (daemonStatus, error) {
	cfg, err := config.Load()
	if err != nil {
		return daemonStatus{}, err
	}
	st := daemonStatus{State: stateStopped, RunFile: cfg.RunFilePath, DataDir: cfg.DataDir}

	info, err := runfile.Read(cfg.RunFilePath)
	if err != nil {
		return daemonStatus{}, err
	}
	if info == nil {
		return st, nil
	}

	st.PID = info.PID
	st.Port = info.Port
	startedAt := info.StartedAt
	st.StartedAt = &startedAt
	st.Uptime = formatUptime(c.deps.Now().Sub(info.StartedAt))

	if !c.deps.ProcessAlive(info.PID) {
		st.State = stateStale
		st.Error = "run-file points to a dead process"
		return st, nil
	}

	health, err := c.readProbe(ctx, info.Port, "healthz")
	if err != nil {
		st.State = stateUnhealthy
		st.Error = err.Error()
		return st, nil
	}
	if err := verifyProbeOwner(health, info.PID, "healthz"); err != nil {
		st.State = stateStale
		st.Error = err.Error()
		return st, nil
	}
	st.owned = true
	st.Health = health.Status
	if health.Status != "ok" {
		st.State = stateUnhealthy
		return st, nil
	}

	ready, err := c.readProbe(ctx, info.Port, "readyz")
	if err != nil {
		st.State = stateNotReady
		st.Error = err.Error()
		return st, nil
	}
	if err := verifyProbeOwner(ready, info.PID, "readyz"); err != nil {
		st.State = stateStale
		st.owned = false
		st.Error = err.Error()
		return st, nil
	}
	st.Ready = ready.Status
	if ready.Status == string(stateReady) {
		st.State = stateReady
		return st, nil
	}
	st.State = stateNotReady
	return st, nil
}

func (c *commandContext) readProbe(ctx context.Context, port int, path string) (probeResult, error) {
	reqCtx, cancel := context.WithTimeout(ctx, probeTimeout)
	defer cancel()

	req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, fmt.Sprintf("http://%s:%d/%s", config.LoopbackHost, port, path), http.NoBody)
	if err != nil {
		return probeResult{}, err
	}
	resp, err := c.deps.HTTPClient.Do(req)
	if err != nil {
		return probeResult{}, fmt.Errorf("%s: %w", path, err)
	}
	defer func() { _ = resp.Body.Close() }()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return probeResult{}, fmt.Errorf("%s: HTTP %d", path, resp.StatusCode)
	}
	var body probeResult
	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
		return probeResult{}, fmt.Errorf("%s: decode response: %w", path, err)
	}
	if body.Status == "" {
		return probeResult{}, fmt.Errorf("%s: missing status", path)
	}
	return body, nil
}

func verifyProbeOwner(probe probeResult, wantPID int, path string) error {
	if probe.Service != daemonmeta.ServiceName {
		return fmt.Errorf("%s: response is not from AO daemon", path)
	}
	if probe.PID != wantPID {
		return fmt.Errorf("%s: daemon pid %d does not match run-file pid %d", path, probe.PID, wantPID)
	}
	return nil
}

func writeStatus(cmd *cobra.Command, st daemonStatus) error {
	out := cmd.OutOrStdout()
	if _, err := fmt.Fprintf(out, "AO daemon: %s\n", st.State); err != nil {
		return err
	}
	if st.PID != 0 {
		if _, err := fmt.Fprintf(out, "  pid: %d\n", st.PID); err != nil {
			return err
		}
	}
	if st.Port != 0 {
		if _, err := fmt.Fprintf(out, "  port: %d\n", st.Port); err != nil {
			return err
		}
	}
	if st.StartedAt != nil && !st.StartedAt.IsZero() {
		if _, err := fmt.Fprintf(out, "  started: %s\n", st.StartedAt.Format(time.RFC3339)); err != nil {
			return err
		}
	}
	if st.Uptime != "" {
		if _, err := fmt.Fprintf(out, "  uptime: %s\n", st.Uptime); err != nil {
			return err
		}
	}
	if _, err := fmt.Fprintf(out, "  run file: %s\n", st.RunFile); err != nil {
		return err
	}
	if _, err := fmt.Fprintf(out, "  data dir: %s\n", st.DataDir); err != nil {
		return err
	}
	if st.Health != "" {
		if _, err := fmt.Fprintf(out, "  healthz: %s\n", st.Health); err != nil {
			return err
		}
	}
	if st.Ready != "" {
		if _, err := fmt.Fprintf(out, "  readyz: %s\n", st.Ready); err != nil {
			return err
		}
	}
	if st.Error != "" {
		if _, err := fmt.Fprintf(out, "  error: %s\n", st.Error); err != nil {
			return err
		}
	}
	return nil
}

func formatUptime(d time.Duration) string {
	if d < 0 {
		d = 0
	}
	return d.Round(time.Second).String()
}
