package forwarder

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

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
)

const (
	selfDemotionConfirmedJSONField = "selfDemotionConfirmed"
	selfDemotionRequiredCode       = "self_demotion_confirmation_required"
)

type selfDemotionResource string

const (
	selfDemotionResourceKB     selfDemotionResource = "kb"
	selfDemotionResourceMemory selfDemotionResource = "memory"
	selfDemotionResourceSkill  selfDemotionResource = "skills"
)

type selfDemotionAction string

const (
	selfDemotionActionUpsert               selfDemotionAction = "upsert"
	selfDemotionActionDelete               selfDemotionAction = "delete"
	selfDemotionActionKBDefault            selfDemotionAction = "kb_default"
	selfDemotionActionKBRepositoryOverride selfDemotionAction = "kb_repository_override"
)

type optionalString struct {
	Value string
	Set   bool
}

type optionalNullableString struct {
	Value string
	Set   bool
	Null  bool
}

type selfDemotionRepositoryOverride struct {
	SourceID string
	RepoRole string
	Profile  optionalNullableString
}

type selfDemotionMutation struct {
	Resource                       selfDemotionResource
	Action                         selfDemotionAction
	ResourceID                     string
	GrantID                        string
	SubjectType                    string
	SubjectID                      string
	Role                           optionalString
	RepositoryAccessProfile        optionalString
	DefaultRole                    optionalString
	DefaultRepositoryAccessProfile optionalString
	RepositoryOverrides            []selfDemotionRepositoryOverride
}

type forwardedPermissionSourceRepositoryAccess struct {
	SourceID                         string `json:"sourceId"`
	RepoRole                         string `json:"repoRole"`
	InheritedRepositoryAccessProfile string `json:"inheritedRepositoryAccessProfile"`
	EffectiveRepositoryAccessProfile string `json:"effectiveRepositoryAccessProfile"`
}

type forwardedPermissionGrant struct {
	ID                      string                                      `json:"id"`
	SubjectType             string                                      `json:"subjectType"`
	SubjectID               string                                      `json:"subjectId"`
	Role                    string                                      `json:"role"`
	RepositoryAccessProfile string                                      `json:"repositoryAccessProfile"`
	SourceRepositoryAccess  []forwardedPermissionSourceRepositoryAccess `json:"sourceRepositoryAccess"`
}

type forwardedPermissionsResponse struct {
	MyRole  string                     `json:"myRole"`
	Owner   *forwardedPermissionGrant  `json:"owner"`
	Self    *forwardedPermissionGrant  `json:"self"`
	Members []forwardedPermissionGrant `json:"members"`
}

type forwardedKnowledgeBaseDetail struct {
	DefaultWorkspaceMemberRole     string `json:"defaultWorkspaceMemberRole"`
	DefaultRepositoryAccessProfile string `json:"defaultRepositoryAccessProfile"`
}

func (handler *Handler) requireSelfDemotionConfirmation(ctx *gin.Context, authenticated identity.AuthenticatedUser, jwt string) bool {
	mutation, ok := selfDemotionMutationFromRequest(ctx.Request)
	if !ok {
		return true
	}
	body, confirmed, err := readAndSanitizeSelfDemotionBody(ctx.Request)
	if err != nil {
		api.Error(ctx, http.StatusBadRequest, "Invalid permission change request")
		return false
	}
	mutation.applyBody(body)
	if confirmed {
		return true
	}

	userID := strings.TrimSpace(firstNonEmpty(authenticated.User.ID, authenticated.Session.UserID))
	if userID == "" || !mutation.couldAffectUser(userID) {
		return true
	}
	workspaceID, ok := validatedCurrentWorkspaceID(ctx)
	if !ok {
		api.Error(ctx, http.StatusBadRequest, "No workspace selected")
		return false
	}

	permissions, err := handler.fetchSelfDemotionPermissions(ctx.Request.Context(), jwt, workspaceID, mutation)
	if err != nil {
		if handler.logger != nil {
			handler.logger.WarnContext(ctx.Request.Context(), "self-demotion guard permission lookup failed", "path", ctx.Request.URL.Path, "error", err)
		}
		api.Error(ctx, http.StatusBadGateway, "Failed to verify permission change")
		return false
	}
	kbDefaultRole := ""
	kbDefaultRepositoryAccessProfile := ""
	needsKBDetail := mutation.Resource == selfDemotionResourceKB &&
		(mutation.Action == selfDemotionActionDelete ||
			(mutation.Action == selfDemotionActionKBDefault && mutation.DefaultRepositoryAccessProfile.Set))
	if needsKBDetail {
		kbDetail, err := handler.fetchSelfDemotionKnowledgeBase(ctx.Request.Context(), jwt, workspaceID, mutation.ResourceID)
		if err != nil {
			if handler.logger != nil {
				handler.logger.WarnContext(ctx.Request.Context(), "self-demotion guard KB lookup failed", "knowledge_base_id", mutation.ResourceID, "error", err)
			}
			api.Error(ctx, http.StatusBadGateway, "Failed to verify permission change")
			return false
		}
		kbDefaultRole = strings.TrimSpace(kbDetail.DefaultWorkspaceMemberRole)
		if kbDefaultRole == "" {
			kbDefaultRole = "view"
		}
		kbDefaultRepositoryAccessProfile = strings.TrimSpace(kbDetail.DefaultRepositoryAccessProfile)
		if kbDefaultRepositoryAccessProfile == "" {
			kbDefaultRepositoryAccessProfile = "none"
		}
	}
	if !mutation.wouldSelfDemote(permissions, userID, kbDefaultRole, kbDefaultRepositoryAccessProfile) {
		return true
	}
	api.ErrorWithCode(ctx, http.StatusConflict, "Self-demotion requires explicit confirmation", selfDemotionRequiredCode)
	return false
}

func selfDemotionMutationFromRequest(request *http.Request) (selfDemotionMutation, bool) {
	if request == nil || request.URL == nil {
		return selfDemotionMutation{}, false
	}
	method := strings.ToUpper(strings.TrimSpace(request.Method))
	if method != http.MethodPost && method != http.MethodPatch && method != http.MethodDelete {
		return selfDemotionMutation{}, false
	}
	segments, ok := escapedPathSegments(request.URL.EscapedPath())
	if !ok || len(segments) < 4 || segments[0] != "api" || segments[1] != "v1" {
		return selfDemotionMutation{}, false
	}

	switch segments[2] {
	case "memory":
		return permissionGrantMutation(method, selfDemotionResourceMemory, segments)
	case "skills":
		return permissionGrantMutation(method, selfDemotionResourceSkill, segments)
	case "kb":
		return kbPermissionMutation(method, segments)
	default:
		return selfDemotionMutation{}, false
	}
}

func permissionGrantMutation(method string, resource selfDemotionResource, segments []string) (selfDemotionMutation, bool) {
	if len(segments) == 6 && segments[4] == "permissions" && segments[5] == "grants" && method == http.MethodPost {
		return selfDemotionMutation{Resource: resource, Action: selfDemotionActionUpsert, ResourceID: segments[3]}, true
	}
	if len(segments) == 7 && segments[4] == "permissions" && segments[5] == "grants" {
		switch method {
		case http.MethodPatch:
			return selfDemotionMutation{Resource: resource, Action: selfDemotionActionUpsert, ResourceID: segments[3], GrantID: segments[6]}, true
		case http.MethodDelete:
			return selfDemotionMutation{Resource: resource, Action: selfDemotionActionDelete, ResourceID: segments[3], GrantID: segments[6]}, true
		}
	}
	return selfDemotionMutation{}, false
}

func kbPermissionMutation(method string, segments []string) (selfDemotionMutation, bool) {
	if len(segments) == 4 && method == http.MethodPatch {
		return selfDemotionMutation{Resource: selfDemotionResourceKB, Action: selfDemotionActionKBDefault, ResourceID: segments[3]}, true
	}
	if len(segments) == 6 && segments[4] == "permissions" && segments[5] == "grants" && method == http.MethodPost {
		return selfDemotionMutation{Resource: selfDemotionResourceKB, Action: selfDemotionActionUpsert, ResourceID: segments[3]}, true
	}
	if len(segments) == 7 && segments[4] == "permissions" && segments[5] == "grants" {
		switch method {
		case http.MethodPatch:
			return selfDemotionMutation{Resource: selfDemotionResourceKB, Action: selfDemotionActionUpsert, ResourceID: segments[3], GrantID: segments[6]}, true
		case http.MethodDelete:
			return selfDemotionMutation{Resource: selfDemotionResourceKB, Action: selfDemotionActionDelete, ResourceID: segments[3], GrantID: segments[6]}, true
		}
	}
	if len(segments) == 8 && segments[4] == "permissions" && segments[5] == "grants" && segments[7] == "repository-overrides" && method == http.MethodPatch {
		return selfDemotionMutation{Resource: selfDemotionResourceKB, Action: selfDemotionActionKBRepositoryOverride, ResourceID: segments[3], GrantID: segments[6]}, true
	}
	return selfDemotionMutation{}, false
}

func escapedPathSegments(escapedPath string) ([]string, bool) {
	trimmed := strings.Trim(strings.TrimSpace(escapedPath), "/")
	if trimmed == "" {
		return nil, false
	}
	rawSegments := strings.Split(trimmed, "/")
	segments := make([]string, 0, len(rawSegments))
	for _, raw := range rawSegments {
		segment, err := url.PathUnescape(raw)
		if err != nil || strings.TrimSpace(segment) == "" {
			return nil, false
		}
		segments = append(segments, segment)
	}
	return segments, true
}

func readAndSanitizeSelfDemotionBody(request *http.Request) (map[string]any, bool, error) {
	if request == nil || request.Body == nil || request.Body == http.NoBody {
		return map[string]any{}, false, nil
	}
	raw, err := io.ReadAll(io.LimitReader(request.Body, 1<<20))
	_ = request.Body.Close()
	if err != nil {
		return nil, false, err
	}
	resetRequestBody(request, raw)
	if len(bytes.TrimSpace(raw)) == 0 {
		return map[string]any{}, false, nil
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	var body map[string]any
	if err := decoder.Decode(&body); err != nil {
		return nil, false, err
	}
	confirmedValue, hadConfirmedField := body[selfDemotionConfirmedJSONField]
	if !hadConfirmedField {
		return body, false, nil
	}
	confirmed := selfDemotionConfirmed(confirmedValue)
	delete(body, selfDemotionConfirmedJSONField)
	if len(body) == 0 && strings.EqualFold(request.Method, http.MethodDelete) {
		resetRequestBody(request, nil)
		return body, confirmed, nil
	}
	sanitized, err := json.Marshal(body)
	if err != nil {
		return nil, false, err
	}
	resetRequestBody(request, sanitized)
	return body, confirmed, nil
}

func selfDemotionConfirmed(value any) bool {
	confirmed, ok := value.(bool)
	return ok && confirmed
}

func resetRequestBody(request *http.Request, raw []byte) {
	if len(raw) == 0 {
		request.Body = http.NoBody
		request.ContentLength = 0
		request.GetBody = func() (io.ReadCloser, error) {
			return http.NoBody, nil
		}
		return
	}
	request.Body = io.NopCloser(bytes.NewReader(raw))
	request.ContentLength = int64(len(raw))
	request.GetBody = func() (io.ReadCloser, error) {
		return io.NopCloser(bytes.NewReader(raw)), nil
	}
}

func (mutation *selfDemotionMutation) applyBody(body map[string]any) {
	mutation.SubjectType = stringJSONField(body, "subjectType")
	mutation.SubjectID = stringJSONField(body, "subjectId")
	mutation.Role = optionalStringJSONField(body, "role")
	mutation.RepositoryAccessProfile = optionalStringJSONField(body, "repositoryAccessProfile")
	mutation.DefaultRole = optionalStringJSONField(body, "defaultWorkspaceMemberRole")
	mutation.DefaultRepositoryAccessProfile = optionalStringJSONField(body, "defaultRepositoryAccessProfile")
	mutation.RepositoryOverrides = repositoryOverridesJSONField(body, "repositoryOverrides")
	if len(mutation.RepositoryOverrides) == 0 {
		mutation.RepositoryOverrides = repositoryOverridesJSONField(body, "items")
	}
}

func stringJSONField(body map[string]any, key string) string {
	value, ok := body[key]
	if !ok {
		return ""
	}
	text, ok := value.(string)
	if !ok {
		return ""
	}
	return strings.TrimSpace(text)
}

func optionalStringJSONField(body map[string]any, key string) optionalString {
	value, ok := body[key]
	if !ok || value == nil {
		return optionalString{}
	}
	text, ok := value.(string)
	if !ok {
		return optionalString{}
	}
	return optionalString{Value: strings.TrimSpace(text), Set: true}
}

func repositoryOverridesJSONField(body map[string]any, key string) []selfDemotionRepositoryOverride {
	rawItems, ok := body[key].([]any)
	if !ok {
		return nil
	}
	items := make([]selfDemotionRepositoryOverride, 0, len(rawItems))
	for _, rawItem := range rawItems {
		item, ok := rawItem.(map[string]any)
		if !ok {
			continue
		}
		override := selfDemotionRepositoryOverride{
			SourceID: stringJSONField(item, "sourceId"),
			RepoRole: stringJSONField(item, "repoRole"),
			Profile:  optionalNullableStringJSONField(item, "repositoryAccessProfile"),
		}
		if override.SourceID == "" || override.RepoRole == "" || !override.Profile.Set {
			continue
		}
		items = append(items, override)
	}
	return items
}

func optionalNullableStringJSONField(body map[string]any, key string) optionalNullableString {
	value, ok := body[key]
	if !ok {
		return optionalNullableString{}
	}
	if value == nil {
		return optionalNullableString{Set: true, Null: true}
	}
	text, ok := value.(string)
	if !ok {
		return optionalNullableString{}
	}
	return optionalNullableString{Value: strings.TrimSpace(text), Set: true}
}

func (mutation selfDemotionMutation) couldAffectUser(userID string) bool {
	switch mutation.Action {
	case selfDemotionActionKBDefault:
		return mutation.DefaultRole.Set || mutation.DefaultRepositoryAccessProfile.Set
	case selfDemotionActionDelete, selfDemotionActionKBRepositoryOverride:
		return true
	case selfDemotionActionUpsert:
		if mutation.SubjectType != "" && mutation.SubjectType != "user" {
			return false
		}
		return mutation.SubjectID == "" || mutation.SubjectID == userID
	default:
		return false
	}
}

func (mutation selfDemotionMutation) wouldSelfDemote(permissions forwardedPermissionsResponse, userID string, kbDefaultRole string, kbDefaultRepositoryAccessProfile string) bool {
	switch mutation.Action {
	case selfDemotionActionKBDefault:
		if permissions.grantForUser(userID) != nil {
			return false
		}
		if mutation.DefaultRole.Set && productRoleRank(mutation.Resource, mutation.DefaultRole.Value) < productRoleRank(mutation.Resource, permissions.MyRole) {
			return true
		}
		return mutation.DefaultRepositoryAccessProfile.Set &&
			repositoryAccessRank(mutation.DefaultRepositoryAccessProfile.Value) < repositoryAccessRank(kbDefaultRepositoryAccessProfile)
	case selfDemotionActionUpsert:
		grant := permissions.grantForMutation(userID, mutation)
		currentRole := permissions.MyRole
		if grant != nil && strings.TrimSpace(grant.Role) != "" {
			currentRole = grant.Role
		}
		if mutation.Role.Set && productRoleRank(mutation.Resource, mutation.Role.Value) < productRoleRank(mutation.Resource, currentRole) {
			return true
		}
		if mutation.Resource != selfDemotionResourceKB || grant == nil {
			return false
		}
		if mutation.RepositoryAccessProfile.Set && repositoryAccessRank(mutation.RepositoryAccessProfile.Value) < repositoryAccessRank(grant.RepositoryAccessProfile) {
			return true
		}
		return sourceRepositoryOverridesWouldDemote(*grant, mutation.RepositoryOverrides)
	case selfDemotionActionKBRepositoryOverride:
		grant := permissions.grantByID(mutation.GrantID)
		return grant != nil && grant.isUser(userID) && sourceRepositoryOverridesWouldDemote(*grant, mutation.RepositoryOverrides)
	case selfDemotionActionDelete:
		grant := permissions.grantByID(mutation.GrantID)
		if grant == nil || !grant.isUser(userID) {
			return false
		}
		if mutation.Resource == selfDemotionResourceKB {
			if productRoleRank(mutation.Resource, kbDefaultRole) < productRoleRank(mutation.Resource, grant.Role) {
				return true
			}
			return grant.hasRepositoryAccess()
		}
		return productRoleRank(mutation.Resource, "none") < productRoleRank(mutation.Resource, grant.Role)
	default:
		return false
	}
}

func (permissions forwardedPermissionsResponse) grantForMutation(userID string, mutation selfDemotionMutation) *forwardedPermissionGrant {
	if mutation.GrantID != "" {
		return permissions.grantByID(mutation.GrantID)
	}
	return permissions.grantForUser(userID)
}

func (permissions forwardedPermissionsResponse) grantByID(grantID string) *forwardedPermissionGrant {
	grantID = strings.TrimSpace(grantID)
	if grantID == "" {
		return nil
	}
	for _, grant := range permissions.allGrants() {
		if strings.TrimSpace(grant.ID) == grantID {
			return grant
		}
	}
	return nil
}

func (permissions forwardedPermissionsResponse) grantForUser(userID string) *forwardedPermissionGrant {
	userID = strings.TrimSpace(userID)
	if userID == "" {
		return nil
	}
	for _, grant := range permissions.allGrants() {
		if grant.isUser(userID) {
			return grant
		}
	}
	return nil
}

func (permissions forwardedPermissionsResponse) allGrants() []*forwardedPermissionGrant {
	grants := make([]*forwardedPermissionGrant, 0, len(permissions.Members)+2)
	if permissions.Self != nil {
		grants = append(grants, permissions.Self)
	}
	if permissions.Owner != nil && (permissions.Self == nil || permissions.Owner.ID != permissions.Self.ID) {
		grants = append(grants, permissions.Owner)
	}
	for index := range permissions.Members {
		grants = append(grants, &permissions.Members[index])
	}
	return grants
}

func (grant forwardedPermissionGrant) isUser(userID string) bool {
	subjectType := strings.TrimSpace(grant.SubjectType)
	if subjectType != "user" {
		return false
	}
	return strings.TrimSpace(grant.SubjectID) == strings.TrimSpace(userID)
}

func (grant forwardedPermissionGrant) hasRepositoryAccess() bool {
	if repositoryAccessRank(grant.RepositoryAccessProfile) > 0 {
		return true
	}
	for _, row := range grant.SourceRepositoryAccess {
		if repositoryAccessRank(row.EffectiveRepositoryAccessProfile) > 0 {
			return true
		}
	}
	return false
}

func sourceRepositoryOverridesWouldDemote(grant forwardedPermissionGrant, overrides []selfDemotionRepositoryOverride) bool {
	if len(overrides) == 0 {
		return false
	}
	for _, override := range overrides {
		row, ok := grant.sourceRepositoryAccess(override.SourceID, override.RepoRole)
		if !ok {
			continue
		}
		nextProfile := override.Profile.Value
		if override.Profile.Null {
			nextProfile = row.InheritedRepositoryAccessProfile
		}
		if repositoryAccessRank(nextProfile) < repositoryAccessRank(row.EffectiveRepositoryAccessProfile) {
			return true
		}
	}
	return false
}

func (grant forwardedPermissionGrant) sourceRepositoryAccess(sourceID string, repoRole string) (forwardedPermissionSourceRepositoryAccess, bool) {
	sourceID = strings.TrimSpace(sourceID)
	repoRole = strings.TrimSpace(repoRole)
	for _, row := range grant.SourceRepositoryAccess {
		if strings.TrimSpace(row.SourceID) == sourceID && strings.TrimSpace(row.RepoRole) == repoRole {
			return row, true
		}
	}
	return forwardedPermissionSourceRepositoryAccess{}, false
}

func productRoleRank(resource selfDemotionResource, role string) int {
	switch strings.TrimSpace(role) {
	case "owner":
		if resource == selfDemotionResourceKB {
			return 4
		}
		return 3
	case "manage":
		if resource == selfDemotionResourceKB {
			return 3
		}
		return 2
	case "edit":
		if resource == selfDemotionResourceKB {
			return 2
		}
		return 2
	case "view":
		return 1
	default:
		return 0
	}
}

func repositoryAccessRank(profile string) int {
	switch strings.TrimSpace(profile) {
	case "manage":
		return 3
	case "write":
		return 2
	case "read":
		return 1
	default:
		return 0
	}
}

func (handler *Handler) fetchSelfDemotionPermissions(ctx context.Context, jwt string, workspaceID string, mutation selfDemotionMutation) (forwardedPermissionsResponse, error) {
	var response forwardedPermissionsResponse
	err := handler.getForwardedJSON(ctx, jwt, workspaceID, mutation.permissionsPath(), &response)
	return response, err
}

func (handler *Handler) fetchSelfDemotionKnowledgeBase(ctx context.Context, jwt string, workspaceID string, knowledgeBaseID string) (forwardedKnowledgeBaseDetail, error) {
	var response forwardedKnowledgeBaseDetail
	err := handler.getForwardedJSON(ctx, jwt, workspaceID, "/api/v1/kb/"+url.PathEscape(strings.TrimSpace(knowledgeBaseID)), &response)
	return response, err
}

func (mutation selfDemotionMutation) permissionsPath() string {
	switch mutation.Resource {
	case selfDemotionResourceMemory:
		return "/api/v1/memory/" + url.PathEscape(strings.TrimSpace(mutation.ResourceID)) + "/permissions"
	case selfDemotionResourceSkill:
		return "/api/v1/skills/" + url.PathEscape(strings.TrimSpace(mutation.ResourceID)) + "/permissions"
	case selfDemotionResourceKB:
		return "/api/v1/kb/" + url.PathEscape(strings.TrimSpace(mutation.ResourceID)) + "/permissions"
	default:
		return ""
	}
}

func (handler *Handler) getForwardedJSON(ctx context.Context, jwt string, workspaceID string, path string, response any) error {
	if handler == nil || handler.baseURL == nil {
		return fmt.Errorf("API forwarder is not configured")
	}
	endpoint := *handler.baseURL
	endpoint.Path = singleJoiningSlash(handler.baseURL.Path, path)
	request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
	if err != nil {
		return err
	}
	authHeader := "Bearer " + strings.TrimSpace(jwt)
	request.Header.Set("Accept", "application/json")
	request.Header.Set("Authorization", authHeader)
	request.Header.Set(trustedJWTHeader, authHeader)
	if strings.TrimSpace(workspaceID) != "" {
		request.Header.Set(api.HeaderWorkspaceID, workspaceID)
	}
	client := handler.httpClient
	if client == nil {
		client = &http.Client{Timeout: defaultProxyTimeout}
	}
	res, err := client.Do(request)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusMultipleChoices {
		return upstreamHTTPError(res)
	}
	if err := json.NewDecoder(res.Body).Decode(response); err != nil && err != io.EOF {
		return err
	}
	return nil
}
