package daemon

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"runtime"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/tidbcloud/AgentTeam/desktop/internal/daemoncontrol"
	"github.com/tidbcloud/AgentTeam/x/proc"
)

// MaxPID is a conservative upper bound for valid Unix PIDs.
const MaxPID = 1 << 22

const (
	controlIdentityPath = "/api/internal/control/v2/identity"
	controlTokenHeader  = "X-AgentTeam-Control-Token"
)

// Status is the daemon discovery DTO. Receipt identity fields are populated
// only after the receipt's native process lifetime has been verified.
type Status struct {
	Running           bool                    `json:"running"`
	Healthy           bool                    `json:"healthy"`
	PID               int                     `json:"pid"`
	Host              string                  `json:"host,omitempty"`
	Port              int                     `json:"port,omitempty"`
	Executable        string                  `json:"executable,omitempty"`
	StartTime         time.Time               `json:"start_time"`
	InstanceID        string                  `json:"instance_id,omitempty"`
	DesktopAPIVersion int                     `json:"desktop_api_version,omitempty"`
	BaseURL           string                  `json:"base_url,omitempty"`
	OwnerKind         daemoncontrol.OwnerKind `json:"owner_kind,omitempty"`
}

type statusReceiptReader interface {
	Read() (daemoncontrol.Receipt, error)
}

type statusIdentity = daemoncontrol.Identity

var (
	defaultStatusReceiptStore = func() statusReceiptReader { return daemoncontrol.DefaultReceiptStore() }
	lookupStatusProcess       = proc.ProcessByPID
	probeStatusIdentity       = probeControlIdentity
	statusHTTPClient          = &http.Client{
		Timeout: 2 * time.Second,
		CheckRedirect: func(*http.Request, []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}
)

// NewStatus discovers the daemon exclusively through its private runtime
// receipt. It never falls back to PID files, configuration, health endpoints,
// or process-table scans.
func NewStatus() *Status {
	receipt, err := defaultStatusReceiptStore().Read()
	if err != nil {
		return &Status{}
	}
	// Read validates production receipts. Validate again so injected readers
	// cannot bypass the same fail-closed boundary.
	if err := receipt.Validate(); err != nil {
		return &Status{}
	}

	process, found := lookupStatusProcess(receipt.PID)
	if !found || !receipt.Identity().Matches(process) {
		return &Status{}
	}

	status := statusFromReceipt(receipt)
	identity, err := probeStatusIdentity(context.Background(), receipt)
	if err == nil && statusIdentityMatchesReceipt(identity, receipt) {
		status.Healthy = true
	}
	return status
}

func statusFromReceipt(receipt daemoncontrol.Receipt) *Status {
	status := &Status{
		Running:           true,
		PID:               receipt.PID,
		StartTime:         receipt.StartedAt,
		InstanceID:        receipt.InstanceID,
		DesktopAPIVersion: receipt.DesktopAPIVersion,
		BaseURL:           receipt.BaseURL,
		OwnerKind:         receipt.Owner.Kind,
	}
	if parsed, err := url.Parse(receipt.BaseURL); err == nil {
		status.Host = parsed.Hostname()
		status.Port, _ = strconv.Atoi(parsed.Port())
	}
	return status
}

func statusIdentityFromReceipt(receipt daemoncontrol.Receipt) statusIdentity {
	return statusIdentity{
		InstanceID:        receipt.InstanceID,
		DesktopAPIVersion: receipt.DesktopAPIVersion,
		PID:               receipt.PID,
		StartedAt:         receipt.StartedAt,
		BaseURL:           receipt.BaseURL,
		OwnerKind:         receipt.Owner.Kind,
	}
}

func statusIdentityMatchesReceipt(identity statusIdentity, receipt daemoncontrol.Receipt) bool {
	identityBaseURL, err := daemoncontrol.NormalizeBaseURL(identity.BaseURL)
	if err != nil {
		return false
	}
	return identity.InstanceID == receipt.InstanceID &&
		identity.DesktopAPIVersion == receipt.DesktopAPIVersion &&
		identity.PID == receipt.PID &&
		identity.StartedAt.Equal(receipt.StartedAt) &&
		identityBaseURL == receipt.BaseURL &&
		identity.OwnerKind == receipt.Owner.Kind &&
		!identity.Draining
}

func probeControlIdentity(ctx context.Context, receipt daemoncontrol.Receipt) (statusIdentity, error) {
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, receipt.BaseURL+controlIdentityPath, nil)
	if err != nil {
		return statusIdentity{}, err
	}
	req.Header.Set(controlTokenHeader, receipt.ControlToken)
	resp, err := statusHTTPClient.Do(req)
	if err != nil {
		return statusIdentity{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK || !strings.Contains(strings.ToLower(resp.Header.Get("Content-Type")), "application/json") {
		return statusIdentity{}, fmt.Errorf("control identity returned HTTP %d", resp.StatusCode)
	}
	var identity statusIdentity
	decoder := json.NewDecoder(resp.Body)
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&identity); err != nil {
		return statusIdentity{}, err
	}
	var trailing any
	if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
		return statusIdentity{}, fmt.Errorf("control identity contains trailing JSON")
	}
	return identity, nil
}

// StopUnhealthyProcess terminates only the process lifetime identified by the
// current receipt. A PID alone is never sufficient authorization.
func StopUnhealthyProcess(pid int, timeout time.Duration) error {
	if pid <= 0 {
		return fmt.Errorf("invalid pid %d", pid)
	}
	store := daemoncontrol.DefaultReceiptStore()
	receipt, err := store.Read()
	if err != nil {
		return fmt.Errorf("read daemon receipt: %w", err)
	}
	if receipt.PID != pid {
		return fmt.Errorf("refusing to stop pid %d because it does not match the daemon receipt", pid)
	}
	processInfo, found := proc.ProcessByPID(pid)
	if !found || !receipt.Identity().Matches(processInfo) {
		return fmt.Errorf("refusing to stop pid %d because its process identity is not verified", pid)
	}

	process, err := os.FindProcess(pid)
	if err != nil {
		return fmt.Errorf("find process %d: %w", pid, err)
	}
	if runtime.GOOS == "windows" {
		if err := process.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) {
			return fmt.Errorf("kill unhealthy process %d: %w", pid, err)
		}
	} else if err := process.Signal(syscall.SIGTERM); err != nil && !errors.Is(err, os.ErrProcessDone) {
		return fmt.Errorf("signal unhealthy process %d: %w", pid, err)
	}
	if !waitForPIDExit(pid, timeout, 300*time.Millisecond) {
		if err := process.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) {
			return fmt.Errorf("kill unhealthy process %d after graceful timeout: %w", pid, err)
		}
		if !waitForPIDExit(pid, 3*time.Second, 200*time.Millisecond) {
			return fmt.Errorf("unhealthy process %d did not exit after termination", pid)
		}
	}
	if err := store.DeleteMatching(receipt.InstanceID, receipt.Identity()); err != nil && !errors.Is(err, daemoncontrol.ErrReceiptMissing) {
		return fmt.Errorf("delete stopped daemon receipt: %w", err)
	}
	return nil
}

func waitForPIDExit(pid int, timeout, interval time.Duration) bool {
	if !checkPID(pid) {
		return true
	}
	if timeout <= 0 || interval <= 0 {
		return false
	}
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		time.Sleep(interval)
		if !checkPID(pid) {
			return true
		}
	}
	return !checkPID(pid)
}
