package dash

import (
	"context"
	"errors"
	"fmt"

	"github.com/seaweedfs/seaweedfs/weed/credential"
	"github.com/seaweedfs/seaweedfs/weed/glog"
	"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
)

// cloneGroup creates a deep copy of an iam_pb.Group to avoid mutating stored state.
func cloneGroup(g *iam_pb.Group) *iam_pb.Group {
	clone := &iam_pb.Group{
		Name:     g.Name,
		Disabled: g.Disabled,
	}
	if g.Members != nil {
		clone.Members = make([]string, len(g.Members))
		copy(clone.Members, g.Members)
	}
	if g.PolicyNames != nil {
		clone.PolicyNames = make([]string, len(g.PolicyNames))
		copy(clone.PolicyNames, g.PolicyNames)
	}
	return clone
}

func (s *AdminServer) GetGroups(ctx context.Context) ([]GroupData, error) {
	if s.credentialManager == nil {
		return nil, fmt.Errorf("credential manager not available")
	}

	groupNames, err := s.credentialManager.ListGroups(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to list groups: %w", err)
	}

	var groups []GroupData
	for _, name := range groupNames {
		g, err := s.credentialManager.GetGroup(ctx, name)
		if err != nil {
			if errors.Is(err, credential.ErrGroupNotFound) {
				glog.V(1).Infof("Group %s listed but not found, skipping", name)
				continue
			}
			return nil, fmt.Errorf("failed to get group %s: %w", name, err)
		}
		status := "enabled"
		if g.Disabled {
			status = "disabled"
		}
		groups = append(groups, GroupData{
			Name:        g.Name,
			MemberCount: len(g.Members),
			PolicyCount: len(g.PolicyNames),
			Status:      status,
			Members:     g.Members,
			PolicyNames: g.PolicyNames,
		})
	}
	return groups, nil
}

func (s *AdminServer) GetGroupDetails(ctx context.Context, name string) (*GroupData, error) {
	if s.credentialManager == nil {
		return nil, fmt.Errorf("credential manager not available")
	}

	g, err := s.credentialManager.GetGroup(ctx, name)
	if err != nil {
		return nil, fmt.Errorf("failed to get group: %w", err)
	}
	status := "enabled"
	if g.Disabled {
		status = "disabled"
	}
	return &GroupData{
		Name:        g.Name,
		MemberCount: len(g.Members),
		PolicyCount: len(g.PolicyNames),
		Status:      status,
		Members:     g.Members,
		PolicyNames: g.PolicyNames,
	}, nil
}

func (s *AdminServer) CreateGroup(ctx context.Context, name string) (*GroupData, error) {
	if s.credentialManager == nil {
		return nil, fmt.Errorf("credential manager not available")
	}

	group := &iam_pb.Group{Name: name}
	if err := s.credentialManager.CreateGroup(ctx, group); err != nil {
		return nil, fmt.Errorf("failed to create group: %w", err)
	}
	glog.V(1).Infof("Created group %s", group.Name)
	return &GroupData{
		Name:   group.Name,
		Status: "enabled",
	}, nil
}

func (s *AdminServer) DeleteGroup(ctx context.Context, name string) error {
	if s.credentialManager == nil {
		return fmt.Errorf("credential manager not available")
	}
	// Check for members and attached policies before deleting (same guards as IAM handlers)
	g, err := s.credentialManager.GetGroup(ctx, name)
	if err != nil {
		return fmt.Errorf("failed to get group: %w", err)
	}
	if len(g.Members) > 0 {
		return fmt.Errorf("cannot delete group %s: group has %d member(s): %w", name, len(g.Members), credential.ErrGroupNotEmpty)
	}
	if len(g.PolicyNames) > 0 {
		return fmt.Errorf("cannot delete group %s: group has %d attached policy(ies): %w", name, len(g.PolicyNames), credential.ErrGroupNotEmpty)
	}
	if err := s.credentialManager.DeleteGroup(ctx, name); err != nil {
		return fmt.Errorf("failed to delete group: %w", err)
	}
	glog.V(1).Infof("Deleted group %s", name)
	return nil
}

func (s *AdminServer) AddGroupMember(ctx context.Context, groupName, username string) error {
	if s.credentialManager == nil {
		return fmt.Errorf("credential manager not available")
	}
	g, err := s.credentialManager.GetGroup(ctx, groupName)
	if err != nil {
		return fmt.Errorf("failed to get group: %w", err)
	}
	g = cloneGroup(g)
	if _, err := s.credentialManager.GetUser(ctx, username); err != nil {
		return fmt.Errorf("user %s not found: %w", username, err)
	}
	for _, m := range g.Members {
		if m == username {
			return nil // already a member
		}
	}
	g.Members = append(g.Members, username)
	if err := s.credentialManager.UpdateGroup(ctx, g); err != nil {
		return fmt.Errorf("failed to update group: %w", err)
	}
	glog.V(1).Infof("Added user %s to group %s", username, groupName)
	return nil
}

func (s *AdminServer) RemoveGroupMember(ctx context.Context, groupName, username string) error {
	if s.credentialManager == nil {
		return fmt.Errorf("credential manager not available")
	}
	g, err := s.credentialManager.GetGroup(ctx, groupName)
	if err != nil {
		return fmt.Errorf("failed to get group: %w", err)
	}
	g = cloneGroup(g)
	found := false
	var newMembers []string
	for _, m := range g.Members {
		if m == username {
			found = true
		} else {
			newMembers = append(newMembers, m)
		}
	}
	if !found {
		return fmt.Errorf("user %s is not a member of group %s: %w", username, groupName, credential.ErrUserNotInGroup)
	}
	g.Members = newMembers
	if err := s.credentialManager.UpdateGroup(ctx, g); err != nil {
		return fmt.Errorf("failed to update group: %w", err)
	}
	glog.V(1).Infof("Removed user %s from group %s", username, groupName)
	return nil
}

func (s *AdminServer) AttachGroupPolicy(ctx context.Context, groupName, policyName string) error {
	if s.credentialManager == nil {
		return fmt.Errorf("credential manager not available")
	}
	g, err := s.credentialManager.GetGroup(ctx, groupName)
	if err != nil {
		return fmt.Errorf("failed to get group: %w", err)
	}
	g = cloneGroup(g)
	if _, err := s.credentialManager.GetPolicy(ctx, policyName); err != nil {
		return fmt.Errorf("policy %s not found: %w", policyName, err)
	}
	for _, p := range g.PolicyNames {
		if p == policyName {
			return nil // already attached
		}
	}
	g.PolicyNames = append(g.PolicyNames, policyName)
	if err := s.credentialManager.UpdateGroup(ctx, g); err != nil {
		return fmt.Errorf("failed to update group: %w", err)
	}
	glog.V(1).Infof("Attached policy %s to group %s", policyName, groupName)
	return nil
}

func (s *AdminServer) DetachGroupPolicy(ctx context.Context, groupName, policyName string) error {
	if s.credentialManager == nil {
		return fmt.Errorf("credential manager not available")
	}
	g, err := s.credentialManager.GetGroup(ctx, groupName)
	if err != nil {
		return fmt.Errorf("failed to get group: %w", err)
	}
	g = cloneGroup(g)
	found := false
	var newPolicies []string
	for _, p := range g.PolicyNames {
		if p == policyName {
			found = true
		} else {
			newPolicies = append(newPolicies, p)
		}
	}
	if !found {
		return fmt.Errorf("policy %s is not attached to group %s: %w", policyName, groupName, credential.ErrPolicyNotAttached)
	}
	g.PolicyNames = newPolicies
	if err := s.credentialManager.UpdateGroup(ctx, g); err != nil {
		return fmt.Errorf("failed to update group: %w", err)
	}
	glog.V(1).Infof("Detached policy %s from group %s", policyName, groupName)
	return nil
}

func (s *AdminServer) SetGroupStatus(ctx context.Context, groupName string, enabled bool) error {
	if s.credentialManager == nil {
		return fmt.Errorf("credential manager not available")
	}
	g, err := s.credentialManager.GetGroup(ctx, groupName)
	if err != nil {
		return fmt.Errorf("failed to get group: %w", err)
	}
	g = cloneGroup(g)
	g.Disabled = !enabled
	if err := s.credentialManager.UpdateGroup(ctx, g); err != nil {
		return fmt.Errorf("failed to update group: %w", err)
	}
	glog.V(1).Infof("Set group %s status to enabled=%v", groupName, enabled)
	return nil
}
