package control

import (
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/daemoncontrol"
	"github.com/tidbcloud/AgentTeam/desktop/internal/token"
)

type handlers struct {
	controller      *daemoncontrol.Controller
	requestShutdown func()
	uiSessions      UISessionControl
}

type OwnerProof struct {
	InstanceID       string    `json:"instanceId"`
	OwnerID          string    `json:"ownerId"`
	OwnerPID         int       `json:"ownerPid"`
	OwnerStartedAt   time.Time `json:"ownerProcessStartedAt"`
	OwnerStartID     string    `json:"ownerProcessStartId"`
	UnexpectedSecret string    `json:"controlToken"`
}

type adoptRequest struct {
	OwnerProof
	NewOwnerID        string    `json:"newOwnerId"`
	NewOwnerPID       int       `json:"newOwnerPid"`
	NewOwnerStartedAt time.Time `json:"newOwnerProcessStartedAt"`
	NewOwnerStartID   string    `json:"newOwnerProcessStartId"`
}

type UISessionResponse struct {
	SessionToken    string    `json:"sessionToken"`
	ExpiresAt       time.Time `json:"expiresAt"`
	RenewAfter      time.Time `json:"renewAfter"`
	LeaseGeneration uint64    `json:"leaseGeneration"`
	Disposition     string    `json:"disposition"`
}

type UISessionControl interface {
	Create(CreateUISessionRequest) (UISessionResponse, error)
	Renew(RenewUISessionRequest) (UISessionResponse, error)
	Ensure(EnsureUISessionRequest) (UISessionResponse, error)
	Revoke(RevokeUISessionRequest) error
	RevokeInstance(instanceID string)
}

type CreateUISessionRequest struct {
	OwnerProof
	WindowID string `json:"windowId"`
}

type RenewUISessionRequest struct {
	OwnerProof
	WindowID     string `json:"windowId"`
	SessionToken string `json:"sessionToken"`
}

type EnsureUISessionRequest struct {
	OwnerProof
	WindowID string `json:"windowId"`
}

type RevokeUISessionRequest struct {
	OwnerProof
	WindowID string `json:"windowId"`
}

func newHandlers(dependencies Dependencies) handlers {
	return handlers{
		controller:      dependencies.Controller,
		requestShutdown: dependencies.RequestShutdown,
		uiSessions:      dependencies.UISessions,
	}
}

func (h handlers) identity(c *gin.Context) {
	if rejectControlTokenOutsideHeader(c) {
		return
	}
	identity, err := h.controller.Identity(c.GetHeader(ControlTokenHeader))
	if err != nil {
		writeControlError(c, err)
		return
	}
	c.JSON(http.StatusOK, identity)
}

func (h handlers) adopt(c *gin.Context) {
	var request adoptRequest
	if !decodeStrictControlJSON(c, &request) {
		return
	}
	credentials, ok := credentialsFrom(c, request.OwnerProof)
	if !ok {
		return
	}
	adoption, err := h.controller.Adopt(credentials, daemoncontrol.Owner{
		Kind: daemoncontrol.OwnerKindElectron,
		ID:   request.NewOwnerID,
		ProcessIdentity: daemoncontrol.ProcessIdentity{
			PID:       request.NewOwnerPID,
			StartedAt: request.NewOwnerStartedAt,
			StartID:   request.NewOwnerStartID,
		},
	})
	if err != nil {
		writeControlError(c, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"instanceId":   h.controller.PublicIdentity().InstanceID,
		"ownerId":      adoption.Owner.ID,
		"controlToken": adoption.ControlToken,
	})
}

func (h handlers) shutdown(c *gin.Context) {
	var request OwnerProof
	if !decodeStrictControlJSON(c, &request) {
		return
	}
	credentials, ok := credentialsFrom(c, request)
	if !ok {
		return
	}
	alreadyDraining, err := h.controller.BeginShutdown(credentials)
	if err != nil {
		writeControlError(c, err)
		return
	}
	c.JSON(http.StatusAccepted, gin.H{
		"status": func() string {
			if alreadyDraining {
				return "draining"
			}
			return "accepted"
		}(),
	})
	if !alreadyDraining && h.requestShutdown != nil {
		// gin has already committed the complete JSON response. Schedule the
		// existing daemon drain asynchronously so http.Server.Shutdown cannot
		// deadlock waiting for this handler.
		go h.requestShutdown()
	}
}

func (h handlers) createUISession(c *gin.Context) {
	if h.uiSessions == nil {
		c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"error": "UI session control is unavailable"})
		return
	}
	var request CreateUISessionRequest
	if !decodeStrictControlJSON(c, &request) || !authorizeOwner(c, h.controller, request.OwnerProof) {
		return
	}
	response, err := h.uiSessions.Create(request)
	if err != nil {
		writeControlError(c, err)
		return
	}
	c.JSON(http.StatusCreated, response)
}

func (h handlers) renewUISession(c *gin.Context) {
	if h.uiSessions == nil {
		c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"error": "UI session control is unavailable"})
		return
	}
	var request RenewUISessionRequest
	if !decodeStrictControlJSON(c, &request) || !authorizeOwner(c, h.controller, request.OwnerProof) {
		return
	}
	response, err := h.uiSessions.Renew(request)
	if err != nil {
		writeControlError(c, err)
		return
	}
	c.JSON(http.StatusOK, response)
}

func (h handlers) ensureUISession(c *gin.Context) {
	if h.uiSessions == nil {
		c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"error": "UI session control is unavailable"})
		return
	}
	var request EnsureUISessionRequest
	if !decodeStrictControlJSON(c, &request) || !authorizeOwner(c, h.controller, request.OwnerProof) {
		return
	}
	response, err := h.uiSessions.Ensure(request)
	if err != nil {
		writeControlError(c, err)
		return
	}
	c.JSON(http.StatusOK, response)
}

func (h handlers) revokeUISession(c *gin.Context) {
	if h.uiSessions == nil {
		c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"error": "UI session control is unavailable"})
		return
	}
	var request RevokeUISessionRequest
	if !decodeStrictControlJSON(c, &request) || !authorizeOwner(c, h.controller, request.OwnerProof) {
		return
	}
	if err := h.uiSessions.Revoke(request); err != nil {
		writeControlError(c, err)
		return
	}
	c.Status(http.StatusNoContent)
}

func authorizeOwner(c *gin.Context, controller *daemoncontrol.Controller, proof OwnerProof) bool {
	credentials, ok := credentialsFrom(c, proof)
	if !ok {
		return false
	}
	if err := controller.Authorize(credentials); err != nil {
		writeControlError(c, err)
		return false
	}
	return true
}

func credentialsFrom(c *gin.Context, proof OwnerProof) (daemoncontrol.Credentials, bool) {
	if rejectControlTokenOutsideHeader(c) || strings.TrimSpace(proof.UnexpectedSecret) != "" {
		if !c.IsAborted() {
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "control token is accepted only in X-AgentTeam-Control-Token"})
		}
		return daemoncontrol.Credentials{}, false
	}
	return daemoncontrol.Credentials{
		ControlToken: c.GetHeader(ControlTokenHeader),
		InstanceID:   proof.InstanceID,
		OwnerID:      proof.OwnerID,
		Owner: daemoncontrol.ProcessIdentity{
			PID:       proof.OwnerPID,
			StartedAt: proof.OwnerStartedAt,
			StartID:   proof.OwnerStartID,
		},
	}, true
}

func rejectControlTokenOutsideHeader(c *gin.Context) bool {
	for key := range c.Request.URL.Query() {
		if strings.EqualFold(key, "controlToken") || strings.EqualFold(key, ControlTokenHeader) {
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "control token is accepted only in X-AgentTeam-Control-Token"})
			return true
		}
	}
	return false
}

func decodeStrictControlJSON(c *gin.Context, destination any) bool {
	if rejectControlTokenOutsideHeader(c) {
		return false
	}
	decoder := json.NewDecoder(c.Request.Body)
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(destination); err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid control request"})
		return false
	}
	var trailing any
	if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid trailing control request data"})
		return false
	}
	return true
}

func writeControlError(c *gin.Context, err error) {
	switch {
	case errors.Is(err, daemoncontrol.ErrControlUnauthorized):
		c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
	case errors.Is(err, token.ErrUISessionInvalid):
		c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
			"code":  "UI_SESSION_INVALID",
			"error": "UI session is invalid",
		})
	case errors.Is(err, token.ErrUISessionMismatch):
		c.AbortWithStatusJSON(http.StatusConflict, gin.H{
			"code":  "UI_SESSION_SCOPE_MISMATCH",
			"error": "UI session scope mismatch",
		})
	case errors.Is(err, daemoncontrol.ErrOwnerUnsupported),
		errors.Is(err, daemoncontrol.ErrOwnerActive),
		errors.Is(err, daemoncontrol.ErrOwnerIdentity),
		errors.Is(err, daemoncontrol.ErrControlConflict):
		c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": "control request conflicts with current daemon ownership"})
	default:
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "control request failed"})
	}
}
