package skills

import (
	"context"
	"errors"
	"strings"
	"time"

	store "github.com/tidbcloud/AgentTeam/internal/store/resource/skills"
	"gorm.io/gorm"
)

// SkillPermissionGrant is the service-layer projection of a stored SkillGrant.
type SkillPermissionGrant struct {
	ID          string
	WorkspaceID string
	SkillID     string
	SubjectType string
	SubjectID   string
	Role        string
	CreatedBy   string
	UpdatedBy   string
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

// SkillPermissionsSummary is the permission panel payload for one skill.
type SkillPermissionsSummary struct {
	MyRole    string
	CanManage bool
	Owner     *SkillPermissionGrant
	Members   []SkillPermissionGrant
}

// SkillPermissionGrantMutation is the input for upserting a skill grant.
type SkillPermissionGrantMutation struct {
	GrantID     string
	SubjectType string
	SubjectID   string
	Role        string
}

// skillRole returns the caller's role on a skill: the creator is always the
// owner, then an explicit user grant wins, then a workspace visibility grant.
func (s *Service) skillRole(ctx context.Context, skill store.Skill, subject string) string {
	subject = strings.TrimSpace(subject)
	if subject == "" {
		return ""
	}
	if strings.TrimSpace(skill.CreatedBy) == subject {
		return "owner"
	}
	if s != nil && s.officialSkillLibrariesEnabled && IsOfficialBuiltinSkill(skill) {
		return "view"
	}
	grant, err := s.store.GetEffectiveHumanSkillGrant(ctx, skill.WorkspaceID, skill.ID, subject)
	if err != nil {
		return ""
	}
	return normalizeSkillGrantRole(grant.Role)
}

func (s *Service) ListSkillPermissions(ctx context.Context, workspaceID, subject, skillID string) (SkillPermissionsSummary, error) {
	skill, err := s.GetSkill(ctx, workspaceID, subject, skillID)
	if err != nil {
		return SkillPermissionsSummary{}, err
	}
	myRole := s.skillRole(ctx, skill, subject)
	canGrant := skillRoleCanManage(myRole)
	if !canGrant {
		return SkillPermissionsSummary{}, forbidden("skill requires manage access")
	}
	grants, err := s.store.ListSkillGrants(ctx, workspaceID, skill.ID)
	if err != nil {
		return SkillPermissionsSummary{}, err
	}
	out := SkillPermissionsSummary{MyRole: myRole, CanManage: canGrant, Members: []SkillPermissionGrant{}}
	for _, grant := range grants {
		if grant.SubjectType != "user" {
			continue
		}
		converted := skillServiceGrantFromStore(grant)
		if grant.Role == "owner" && out.Owner == nil {
			owner := converted
			out.Owner = &owner
		}
		out.Members = append(out.Members, converted)
	}
	return out, nil
}

func (s *Service) UpsertSkillPermission(ctx context.Context, workspaceID, subject, skillID string, input SkillPermissionGrantMutation) (result SkillPermissionGrant, err error) {
	skill, err := s.GetSkill(ctx, workspaceID, subject, skillID)
	if err != nil {
		return SkillPermissionGrant{}, err
	}
	if !skillRoleCanManage(s.skillRole(ctx, skill, subject)) {
		return SkillPermissionGrant{}, forbidden("skill requires manage access")
	}
	subjectType := normalizeSkillGrantSubjectType(input.SubjectType)
	subjectID := strings.TrimSpace(input.SubjectID)
	grantID := strings.TrimSpace(input.GrantID)
	var existing store.SkillGrant
	existingFound := false
	if grantID != "" {
		existing, err = s.store.GetSkillGrantByID(ctx, workspaceID, skill.ID, grantID)
		if err != nil {
			return SkillPermissionGrant{}, mapStoreError(err)
		}
		existingFound = true
		subjectType = existing.SubjectType
		subjectID = existing.SubjectID
	}
	role := normalizeSkillGrantRole(input.Role)
	if subjectType == "" {
		return SkillPermissionGrant{}, invalidArgument("permission subject type must be user")
	}
	if subjectType == "agent" {
		return SkillPermissionGrant{}, invalidArgument("agent skill access must use agent skill bindings")
	}
	if subjectID == "" {
		return SkillPermissionGrant{}, invalidArgument("permission subject id is required")
	}
	now := s.now()
	if grantID == "" {
		existing, err = s.store.GetSkillGrant(ctx, workspaceID, skill.ID, subjectType, subjectID)
		if err == nil {
			existingFound = true
		} else if !errors.Is(err, gorm.ErrRecordNotFound) {
			return SkillPermissionGrant{}, err
		}
	}
	eventType := skillActivityGrantAdded
	previousRole := ""
	if existingFound {
		previousRole = existing.Role
		if existing.Role == role {
			eventType = ""
		} else {
			eventType = skillActivityGrantRoleChanged
		}
	}
	writeRoute, err := s.beginSkillWrite(ctx, skill.WorkspaceID, skill.ID)
	if err != nil {
		return SkillPermissionGrant{}, err
	}
	activityRef, err := s.skillActivityRef(ctx, skill, writeRoute.native)
	if err != nil {
		return SkillPermissionGrant{}, err
	}
	var grant store.SkillGrant
	err = s.store.Transaction(ctx, func(tx *store.Store) error {
		var err error
		grant, err = tx.UpsertSkillGrant(ctx, store.SkillGrant{
			ID:          newSkillGrantID(),
			WorkspaceID: strings.TrimSpace(workspaceID),
			SkillID:     skill.ID,
			SubjectType: subjectType,
			SubjectID:   subjectID,
			Role:        role,
			CreatedBy:   strings.TrimSpace(subject),
			UpdatedBy:   strings.TrimSpace(subject),
			CreatedAt:   now,
			UpdatedAt:   now,
		})
		if err != nil {
			return err
		}
		if eventType == "" {
			return nil
		}
		return writeSkillActivity(ctx, s.skillActivityStore(tx), skill, activityRef, eventType, strings.TrimSpace(subject), "grant", grant.ID, map[string]any{
			"grantId":      grant.ID,
			"subjectType":  grant.SubjectType,
			"subjectId":    grant.SubjectID,
			"subjectName":  grant.SubjectID,
			"role":         grant.Role,
			"previousRole": previousRole,
		}, now)
	})
	if err != nil {
		return SkillPermissionGrant{}, err
	}
	return skillServiceGrantFromStore(grant), nil
}

func (s *Service) DeleteSkillPermission(ctx context.Context, workspaceID, subject, skillID, grantID string) (err error) {
	skill, err := s.GetSkill(ctx, workspaceID, subject, skillID)
	if err != nil {
		return err
	}
	if !skillRoleCanManage(s.skillRole(ctx, skill, subject)) {
		return forbidden("skill requires manage access")
	}
	grantID = strings.TrimSpace(grantID)
	if grantID == "" {
		return invalidArgument("permission grant id is required")
	}
	grant, err := s.store.GetSkillGrantByID(ctx, workspaceID, skill.ID, grantID)
	if err != nil {
		return mapStoreError(err)
	}
	if grant.SubjectType == "user" && grant.Role == "owner" {
		return invalidArgument("skill owner grant cannot be removed")
	}
	if grant.SubjectType == "agent" {
		return invalidArgument("agent skill access must use agent skill bindings")
	}
	now := s.now()
	writeRoute, err := s.beginSkillWrite(ctx, skill.WorkspaceID, skill.ID)
	if err != nil {
		return err
	}
	activityRef, err := s.skillActivityRef(ctx, skill, writeRoute.native)
	if err != nil {
		return err
	}
	return mapStoreError(s.store.Transaction(ctx, func(tx *store.Store) error {
		if err := tx.DeleteSkillGrantByID(ctx, workspaceID, skill.ID, grantID); err != nil {
			return err
		}
		return writeSkillActivity(ctx, s.skillActivityStore(tx), skill, activityRef, skillActivityGrantRemoved, strings.TrimSpace(subject), "grant", grant.ID, map[string]any{
			"grantId":     grant.ID,
			"subjectType": grant.SubjectType,
			"subjectId":   grant.SubjectID,
			"subjectName": grant.SubjectID,
			"role":        grant.Role,
		}, now)
	}))
}

func skillServiceGrantFromStore(grant store.SkillGrant) SkillPermissionGrant {
	return SkillPermissionGrant{
		ID:          grant.ID,
		WorkspaceID: grant.WorkspaceID,
		SkillID:     grant.SkillID,
		SubjectType: grant.SubjectType,
		SubjectID:   grant.SubjectID,
		Role:        normalizeSkillGrantRole(grant.Role),
		CreatedBy:   grant.CreatedBy,
		UpdatedBy:   grant.UpdatedBy,
		CreatedAt:   grant.CreatedAt,
		UpdatedAt:   grant.UpdatedAt,
	}
}

func normalizeSkillGrantSubjectType(subjectType string) string {
	switch strings.TrimSpace(subjectType) {
	case "user":
		return strings.TrimSpace(subjectType)
	default:
		return ""
	}
}

func normalizeSkillGrantRole(role string) string {
	switch strings.TrimSpace(role) {
	case "owner", "manage", "view":
		return strings.TrimSpace(role)
	case "edit":
		return "view"
	default:
		return "view"
	}
}
