package agent

import (
	"context"
	"errors"
	"net/http"
	"strconv"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/service/approvalapp"
	"github.com/tidbcloud/AgentTeam/internal/service/role/execution"
)

type RoleManagerResolver interface {
	ResolveManager(context.Context, execution.AgentPrincipal) (execution.Manager, error)
}

type RoleReadRuntime interface {
	List(context.Context, execution.RoleReadPrincipal, execution.RoleListInput) (execution.RoleListResult, error)
	Get(context.Context, execution.RoleReadPrincipal, string) (execution.RoleDetail, error)
}

type RoleRequestRuntime interface {
	RequestGet(context.Context, execution.RequestGetCommand) (execution.RequestResult, error)
}

type AgentRoleHandler struct {
	managers  RoleManagerResolver
	reads     RoleReadRuntime
	requests  RoleRequestRuntime
	approvals ApprovalRuntime
}

type agentRoleGetRequest struct {
	RoleID string `json:"roleId"`
}

type agentRoleRequestGetRequest struct {
	RequestID string `json:"requestId"`
}

type agentRoleCreateRequest struct {
	RequestID   string `json:"requestId"`
	Name        string `json:"name"`
	Definition  string `json:"definition"`
	Description string `json:"description,omitempty"`
}

type agentRoleUpdateRequest struct {
	RequestID             string  `json:"requestId"`
	RoleID                string  `json:"roleId"`
	ExpectedRoleVersionID string  `json:"expectedRoleVersionId"`
	Name                  *string `json:"name,omitempty"`
	Description           *string `json:"description,omitempty"`
	Definition            *string `json:"definition,omitempty"`
	Visibility            string  `json:"visibility,omitempty"`
}

type agentRoleDeleteRequest struct {
	RequestID             string `json:"requestId"`
	RoleID                string `json:"roleId"`
	ExpectedRoleVersionID string `json:"expectedRoleVersionId"`
}

type agentRoleApplyRequest struct {
	RequestID     string `json:"requestId"`
	RoleID        string `json:"roleId"`
	RoleVersionID string `json:"roleVersionId"`
	AgentID       string `json:"agentId"`
}

type agentRoleListResponse struct {
	Roles      []execution.RoleSummary `json:"roles"`
	NextCursor string                  `json:"nextCursor,omitempty"`
}

type agentRoleGetResponse struct {
	Role execution.RoleDetail `json:"role"`
}

type agentRoleFailure struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

type agentRoleRequestGetResponse struct {
	RequestID     string                      `json:"requestId"`
	Action        execution.RoleRequestAction `json:"action"`
	Status        execution.RequestStatus     `json:"status"`
	ApprovalID    string                      `json:"approvalId,omitempty"`
	RoleID        string                      `json:"roleId,omitempty"`
	RoleVersionID string                      `json:"roleVersionId,omitempty"`
	AgentID       string                      `json:"agentId,omitempty"`
	Failure       *agentRoleFailure           `json:"failure,omitempty"`
}

type agentRoleMutationResponse struct {
	ApprovalID string `json:"approvalId"`
	Status     string `json:"status"`
}

func (handler *AgentRoleHandler) List(request api.AgentScopedRequest) (api.Response, error) {
	if handler == nil || handler.managers == nil || handler.reads == nil {
		return api.Response{}, errors.New("Role read runtime is not configured")
	}
	limit, err := parseAgentRoleLimit(request.Gin.Query("limit"))
	if err != nil {
		return api.Response{}, err
	}
	manager, err := handler.managers.ResolveManager(
		request.Context(),
		agentRolePrincipal(request),
	)
	if err != nil {
		return api.Response{}, err
	}
	result, err := handler.reads.List(request.Context(), execution.RoleReadPrincipal{
		WorkspaceID: request.Principal.WorkspaceID, Subject: manager.UserID,
	}, execution.RoleListInput{
		View: request.Gin.Query("view"), Query: request.Gin.Query("q"),
		Limit: limit, Cursor: request.Gin.Query("cursor"),
	})
	if err != nil {
		return api.Response{}, err
	}
	if result.Roles == nil {
		result.Roles = []execution.RoleSummary{}
	}
	return api.OK(agentRoleListResponse{
		Roles: result.Roles, NextCursor: result.NextCursor,
	}), nil
}

func (handler *AgentRoleHandler) Get(request api.AgentScopedJSON[agentRoleGetRequest]) (api.Response, error) {
	if handler == nil || handler.managers == nil || handler.reads == nil {
		return api.Response{}, errors.New("Role read runtime is not configured")
	}
	roleID := strings.TrimSpace(request.Body.RoleID)
	if roleID == "" {
		return api.Response{}, execution.NewRoleReadInputError("roleId is required")
	}
	manager, err := handler.managers.ResolveManager(
		request.Context(),
		agentRolePrincipal(request.AgentScopedRequest),
	)
	if err != nil {
		return api.Response{}, err
	}
	result, err := handler.reads.Get(request.Context(), execution.RoleReadPrincipal{
		WorkspaceID: request.Principal.WorkspaceID, Subject: manager.UserID,
	}, roleID)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(agentRoleGetResponse{Role: result}), nil
}

func (handler *AgentRoleHandler) RequestGet(request api.AgentScopedJSON[agentRoleRequestGetRequest]) (api.Response, error) {
	if handler == nil || handler.requests == nil {
		return api.Response{}, errors.New("Role request runtime is not configured")
	}
	requestID := strings.TrimSpace(request.Body.RequestID)
	if requestID == "" {
		return api.Response{}, execution.NewRoleReadInputError("requestId is required")
	}
	result, err := handler.requests.RequestGet(request.Context(), execution.RequestGetCommand{
		Principal: agentRolePrincipal(request.AgentScopedRequest), RequestID: requestID,
	})
	if err != nil {
		return api.Response{}, err
	}
	response := agentRoleRequestGetResponse{
		RequestID: result.RequestID, Action: result.Action, Status: result.Status,
		ApprovalID: result.ApprovalID, RoleID: result.RoleID,
		RoleVersionID: result.RoleVersionID, AgentID: result.AgentID,
	}
	if result.FailureCode != "" || result.FailureMessage != "" {
		if result.FailureCode == "" || result.FailureMessage == "" {
			return api.Response{}, execution.ErrInvariant
		}
		response.Failure = &agentRoleFailure{
			Code: result.FailureCode, Message: result.FailureMessage,
		}
	}
	return api.OK(response), nil
}

func (handler *AgentRoleHandler) Create(request api.AgentScopedJSON[agentRoleCreateRequest]) (api.Response, error) {
	body := request.Body
	return handler.submit(request.AgentScopedRequest, body.RequestID, execution.RoleCreateProposal{
		Name: body.Name, Definition: body.Definition, Description: body.Description,
	})
}

func (handler *AgentRoleHandler) Update(request api.AgentScopedJSON[agentRoleUpdateRequest]) (api.Response, error) {
	body := request.Body
	return handler.submit(request.AgentScopedRequest, body.RequestID, execution.RoleUpdateProposal{
		RoleID: body.RoleID, ExpectedRoleVersionID: body.ExpectedRoleVersionID,
		Name: body.Name, Description: body.Description, Definition: body.Definition,
		Visibility: body.Visibility,
	})
}

func (handler *AgentRoleHandler) Delete(request api.AgentScopedJSON[agentRoleDeleteRequest]) (api.Response, error) {
	body := request.Body
	return handler.submit(request.AgentScopedRequest, body.RequestID, execution.RoleDeleteProposal{
		RoleID: body.RoleID, ExpectedRoleVersionID: body.ExpectedRoleVersionID,
	})
}

func (handler *AgentRoleHandler) Apply(request api.AgentScopedJSON[agentRoleApplyRequest]) (api.Response, error) {
	body := request.Body
	return handler.submit(request.AgentScopedRequest, body.RequestID, execution.RoleApplyProposal{
		RoleID: body.RoleID, RoleVersionID: body.RoleVersionID, AgentID: body.AgentID,
	})
}

func (handler *AgentRoleHandler) submit(
	request api.AgentScopedRequest,
	requestID string,
	proposal execution.RoleProposal,
) (api.Response, error) {
	if handler == nil || handler.approvals == nil {
		return api.Response{}, errors.New("Role Approval runtime is not configured")
	}
	requestID = strings.TrimSpace(requestID)
	if requestID == "" {
		return api.Response{}, execution.NewRoleReadInputError("requestId is required")
	}
	result, err := handler.approvals.SubmitRequest(
		request.Context(), execution.RequestType, approvalapp.SubmitCommand{
			Principal: approvalapp.SubjectPrincipal{
				ActorType: "agent", WorkspaceID: request.Principal.WorkspaceID,
				PrincipalID: request.Principal.AgentID, MemberID: request.Principal.MemberID,
				MachineID: request.Principal.MachineID,
			},
			ClientRequestID: requestID, Payload: proposal,
			CorrelationID: strings.TrimSpace(request.Gin.GetHeader("X-Request-ID")),
		},
	)
	if err != nil {
		return api.Response{}, err
	}
	public, ok := result.Value.(execution.RequestResult)
	if !ok || strings.TrimSpace(public.ApprovalID) == "" ||
		public.ApprovalID != result.Instance.ID ||
		!isPublicRoleRequestStatus(public.Status) {
		return api.Response{}, execution.ErrInvariant
	}
	if public.Status == execution.RequestPending {
		if _, err := handler.approvals.PresentPending(request.Context(), result); err != nil {
			return api.Response{}, err
		}
	}
	return api.OK(agentRoleMutationResponse{
		ApprovalID: public.ApprovalID, Status: string(public.Status),
	}), nil
}

func isPublicRoleRequestStatus(status execution.RequestStatus) bool {
	switch status {
	case execution.RequestPending, execution.RequestProcessing,
		execution.RequestSucceeded, execution.RequestRejected,
		execution.RequestFailed, execution.RequestCancelled:
		return true
	default:
		return false
	}
}

func parseAgentRoleLimit(raw string) (int, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return 50, nil
	}
	limit, err := strconv.Atoi(raw)
	if err != nil || limit < 1 || limit > 100 {
		return 0, execution.NewRoleReadInputError(
			"limit must be an integer from 1 to 100",
		)
	}
	return limit, nil
}

func roleAgentErrorClassifier(err error) (api.ErrorResponse, bool) {
	response := api.ErrorResponse{Message: "Role request failed", Code: "role_internal"}
	kind, ok := execution.ClassifyError(err)
	if !ok {
		response.Status = http.StatusInternalServerError
		return response, true
	}
	switch kind {
	case execution.ErrorDependency:
		response.Status = http.StatusInternalServerError
	case execution.ErrorInvalidRequest:
		response.Status, response.Code, response.Message =
			http.StatusBadRequest, "role_invalid_request", "Role request is invalid"
	case execution.ErrorInvalidState:
		response.Status, response.Code, response.Message =
			http.StatusUnprocessableEntity, "role_invalid_request", "Role request is invalid"
	case execution.ErrorForbidden:
		response.Status, response.Code, response.Message =
			http.StatusForbidden, "role_forbidden", "Role request is forbidden"
	case execution.ErrorNotFound:
		response.Status, response.Code, response.Message =
			http.StatusNotFound, "role_not_found", "Role request was not found"
	case execution.ErrorConflict:
		response.Status, response.Code, response.Message =
			http.StatusConflict, "role_conflict", "Role request conflicts with current state"
	default:
		response.Status = http.StatusInternalServerError
	}
	return response, true
}

func agentRolePrincipal(request api.AgentScopedRequest) execution.AgentPrincipal {
	return execution.AgentPrincipal{
		AgentID: request.Principal.AgentID, WorkspaceID: request.Principal.WorkspaceID,
		MemberID: request.Principal.MemberID, MachineID: request.Principal.MachineID,
	}
}
