package forwarder

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"

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

type RoleReadGateway struct {
	upstream *Handler
}

func NewRoleReadGateway(deps Dependencies) (*RoleReadGateway, error) {
	upstream, err := NewHandler(deps)
	if err != nil {
		return nil, err
	}
	return &RoleReadGateway{upstream: upstream}, nil
}

func (gateway *RoleReadGateway) List(
	ctx context.Context,
	principal execution.RoleReadPrincipal,
	input execution.RoleListInput,
) (execution.RoleListResult, error) {
	if gateway == nil || gateway.upstream == nil {
		return execution.RoleListResult{}, errors.New("Role read gateway is not configured")
	}
	principal, err := normalizeRoleReadPrincipal(principal)
	if err != nil {
		return execution.RoleListResult{}, err
	}
	page, limit, view, query, err := normalizeRoleListInput(input)
	if err != nil {
		return execution.RoleListResult{}, err
	}
	values := url.Values{}
	values.Set("page", strconv.Itoa(page))
	values.Set("per_page", strconv.Itoa(limit))
	if view != "" {
		values.Set("view", view)
	}
	if query != "" {
		values.Set("q", query)
	}
	var response roleListWire
	err = gateway.upstream.ForwardJSON(ctx, JSONRequest{
		Method:      http.MethodGet,
		Path:        "/api/v1/roles?" + values.Encode(),
		WorkspaceID: principal.WorkspaceID,
		Identity:    roleReadIdentity(principal.Subject),
	}, &response)
	if err != nil {
		return execution.RoleListResult{}, roleReadGatewayError(err)
	}
	if err := response.validate(page, limit); err != nil {
		return execution.RoleListResult{}, err
	}
	result := execution.RoleListResult{
		Roles: make([]execution.RoleSummary, 0, len(response.Items)),
	}
	for _, item := range response.Items {
		if err := item.validate(); err != nil {
			return execution.RoleListResult{}, err
		}
		result.Roles = append(result.Roles, item.summary())
	}
	if response.HasMore {
		result.NextCursor = strconv.Itoa(page + 1)
	}
	return result, nil
}

func (gateway *RoleReadGateway) Get(
	ctx context.Context,
	principal execution.RoleReadPrincipal,
	roleID string,
) (execution.RoleDetail, error) {
	if gateway == nil || gateway.upstream == nil {
		return execution.RoleDetail{}, errors.New("Role read gateway is not configured")
	}
	principal, err := normalizeRoleReadPrincipal(principal)
	if err != nil {
		return execution.RoleDetail{}, err
	}
	roleID = strings.TrimSpace(roleID)
	if roleID == "" {
		return execution.RoleDetail{}, execution.NewRoleReadInputError("Role ID is required")
	}
	if roleID == "." || roleID == ".." || url.PathEscape(roleID) != roleID {
		return execution.RoleDetail{}, execution.NewRoleReadInputError("Role ID is invalid")
	}
	var response roleReadWire
	err = gateway.upstream.ForwardJSON(ctx, JSONRequest{
		Method:      http.MethodGet,
		Path:        "/api/v1/roles/" + roleID,
		WorkspaceID: principal.WorkspaceID,
		Identity:    roleReadIdentity(principal.Subject),
	}, &response)
	if err != nil {
		return execution.RoleDetail{}, roleReadGatewayError(err)
	}
	if err := response.validate(); err != nil {
		return execution.RoleDetail{}, err
	}
	return response.detail(), nil
}

func roleReadGatewayError(err error) error {
	var upstream UpstreamHTTPError
	if errors.As(err, &upstream) {
		switch upstream.StatusCode {
		case http.StatusForbidden, http.StatusNotFound, http.StatusConflict:
			return upstream.Unwrap()
		}
	}
	return execution.NewRoleReadDependencyError(err)
}

func normalizeRoleReadPrincipal(
	principal execution.RoleReadPrincipal,
) (execution.RoleReadPrincipal, error) {
	principal.WorkspaceID = strings.TrimSpace(principal.WorkspaceID)
	principal.Subject = strings.TrimSpace(principal.Subject)
	if principal.WorkspaceID == "" || principal.Subject == "" {
		return execution.RoleReadPrincipal{}, errors.New("Role read principal is incomplete")
	}
	return principal, nil
}

func normalizeRoleListInput(input execution.RoleListInput) (int, int, string, string, error) {
	page := 1
	cursor := strings.TrimSpace(input.Cursor)
	if cursor != "" {
		if len(cursor) > 1 && cursor[0] == '0' {
			return 0, 0, "", "", execution.NewRoleReadInputError("Role list cursor is invalid")
		}
		var err error
		page, err = strconv.Atoi(cursor)
		if err != nil || page <= 0 || strconv.Itoa(page) != cursor {
			return 0, 0, "", "", execution.NewRoleReadInputError("Role list cursor is invalid")
		}
	}
	limit := input.Limit
	if limit == 0 {
		limit = 50
	}
	if limit < 1 || limit > 100 {
		return 0, 0, "", "", execution.NewRoleReadInputError("Role list limit is invalid")
	}
	view := strings.TrimSpace(input.View)
	switch view {
	case "", "all", "workspace", "created_by_me", "private":
	default:
		return 0, 0, "", "", execution.NewRoleReadInputError("Role list view is invalid")
	}
	return page, limit, view, strings.TrimSpace(input.Query), nil
}

func roleReadIdentity(subject string) TrustedIdentity {
	subject = strings.TrimSpace(subject)
	return TrustedIdentity{
		UserID: subject, AgentTeamUserID: subject,
		PreferredUsername: normalizePreferredUsernameCandidate(subject),
	}
}

// roleReadWire mirrors the currently accepted MeshX Role JSON superset without
// importing MeshX internals. Only the minimal fields below are projected.
type roleReadWire struct {
	ID                    string               `json:"id"`
	WorkspaceID           string               `json:"workspaceId,omitempty"`
	Source                string               `json:"source"`
	Visibility            string               `json:"visibility"`
	Slug                  string               `json:"slug"`
	Name                  string               `json:"name"`
	Description           string               `json:"description,omitempty"`
	Locale                string               `json:"locale,omitempty"`
	Scenario              string               `json:"scenario,omitempty"`
	Icon                  string               `json:"icon,omitempty"`
	Tags                  []string             `json:"tags,omitempty"`
	Definition            string               `json:"definition"`
	RuntimeHints          roleReadRuntimeHints `json:"runtimeHints"`
	SourcePresetID        string               `json:"sourcePresetId,omitempty"`
	SourcePresetVersion   int                  `json:"sourcePresetVersion,omitempty"`
	RoleVersionID         string               `json:"roleVersionId,omitempty"`
	SourcePresetVersionID string               `json:"sourcePresetVersionId,omitempty"`
	SourceAgentID         string               `json:"sourceAgentId,omitempty"`
	IsActive              bool                 `json:"isActive"`
	Role                  string               `json:"role,omitempty"`
	CanManage             bool                 `json:"canManage,omitempty"`
	OwnerID               string               `json:"ownerId,omitempty"`
	Editable              bool                 `json:"editable,omitempty"`
	Provenance            roleReadProvenance   `json:"provenance"`
	RolePackage           roleReadPackage      `json:"rolePackage"`
	RolePackageManifest   roleReadManifest     `json:"rolePackageManifest"`
	CreatedBy             string               `json:"createdBy,omitempty"`
	UpdatedBy             string               `json:"updatedBy,omitempty"`
	CreatedAt             string               `json:"createdAt,omitempty"`
	UpdatedAt             string               `json:"updatedAt,omitempty"`
}

type roleReadRuntimeHints struct {
	Runtime         string `json:"runtime,omitempty"`
	Model           string `json:"model,omitempty"`
	ReasoningEffort string `json:"reasoningEffort,omitempty"`
	SuggestedName   string `json:"suggestedName,omitempty"`
}

type roleReadProvenance struct {
	Kind    string `json:"kind"`
	Ref     string `json:"ref"`
	Version int    `json:"version,omitempty"`
}

type roleReadPackage struct {
	Portable    bool             `json:"portable"`
	Sections    []string         `json:"sections"`
	NonPortable []string         `json:"nonPortable,omitempty"`
	Unresolved  []map[string]any `json:"unresolved,omitempty"`
	Manifest    roleReadManifest `json:"manifest"`
}

type roleReadManifest struct {
	Items []roleReadManifestItem `json:"items"`
}

type roleReadManifestItem struct {
	Type        string `json:"type"`
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
	Required    bool   `json:"required,omitempty"`
	Action      string `json:"action,omitempty"`
	ResourceID  string `json:"resourceId,omitempty"`
	AccessLevel string `json:"accessLevel,omitempty"`
	Branch      string `json:"branch,omitempty"`
	Content     string `json:"content,omitempty"`
}

type roleListWire struct {
	Items   []roleReadWire `json:"items"`
	Page    int            `json:"page"`
	PerPage int            `json:"perPage"`
	Total   int64          `json:"total"`
	HasMore bool           `json:"hasMore"`
}

func (wire *roleReadWire) UnmarshalJSON(data []byte) error {
	type alias roleReadWire
	var decoded alias
	if err := decodeStrictRoleReadJSON(data, &decoded); err != nil {
		return err
	}
	*wire = roleReadWire(decoded)
	return nil
}

func (wire *roleListWire) UnmarshalJSON(data []byte) error {
	type alias roleListWire
	var decoded alias
	if err := decodeStrictRoleReadJSON(data, &decoded); err != nil {
		return err
	}
	var fields map[string]json.RawMessage
	if err := json.Unmarshal(data, &fields); err != nil {
		return err
	}
	for _, required := range []string{"items", "page", "perPage", "total", "hasMore"} {
		if _, ok := fields[required]; !ok {
			return errors.New("Role list response is incomplete")
		}
	}
	*wire = roleListWire(decoded)
	return nil
}

func decodeStrictRoleReadJSON(data []byte, target any) error {
	if err := rejectDuplicateRoleOperationJSONKeys(data); err != nil {
		return err
	}
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(target); err != nil {
		return err
	}
	var trailing any
	if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
		if err == nil {
			return errors.New("multiple JSON values")
		}
		return err
	}
	return nil
}

func (wire roleReadWire) validate() error {
	if strings.TrimSpace(wire.ID) == "" ||
		strings.TrimSpace(wire.Name) == "" ||
		strings.TrimSpace(wire.Definition) == "" ||
		strings.TrimSpace(wire.RoleVersionID) == "" {
		return errors.New("Role read response is incomplete")
	}
	switch strings.TrimSpace(wire.Visibility) {
	case "private", "workspace":
	default:
		return errors.New("Role read response visibility is invalid")
	}
	return nil
}

func (wire roleListWire) validate(page, limit int) error {
	if wire.Items == nil || wire.Page != page || wire.PerPage != limit ||
		wire.Total < 0 || (wire.HasMore && wire.Total <= int64(page*limit)) {
		return errors.New("Role list response is incomplete")
	}
	return nil
}

func (wire roleReadWire) summary() execution.RoleSummary {
	return execution.RoleSummary{
		RoleID: wire.ID, Name: wire.Name, Description: wire.Description,
		Visibility: wire.Visibility, RoleVersionID: wire.RoleVersionID,
		CanManage: wire.CanManage,
	}
}

func (wire roleReadWire) detail() execution.RoleDetail {
	return execution.RoleDetail{
		RoleID: wire.ID, Name: wire.Name, Description: wire.Description,
		Definition: wire.Definition, Visibility: wire.Visibility,
		RoleVersionID: wire.RoleVersionID, CanManage: wire.CanManage,
	}
}
