package filer_etc

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"github.com/seaweedfs/seaweedfs/weed/credential"
	"github.com/seaweedfs/seaweedfs/weed/filer"
	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
	"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
)

const IamGroupsDirectory = "groups"

func (store *FilerEtcStore) loadGroupsFromMultiFile(ctx context.Context, s3cfg *iam_pb.S3ApiConfiguration) error {
	return store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		dir := filer.IamConfigDirectory + "/" + IamGroupsDirectory
		entries, err := listEntries(ctx, client, dir)
		if err != nil {
			if errors.Is(err, filer_pb.ErrNotFound) {
				return nil
			}
			return err
		}

		for _, entry := range entries {
			if entry.IsDirectory {
				continue
			}

			var content []byte
			if len(entry.Content) > 0 {
				content = entry.Content
			} else {
				c, err := filer.ReadInsideFiler(ctx, client, dir, entry.Name)
				if err != nil {
					return fmt.Errorf("failed to read group file %s: %w", entry.Name, err)
				}
				content = c
			}

			if len(content) > 0 {
				g := &iam_pb.Group{}
				if err := json.Unmarshal(content, g); err != nil {
					return fmt.Errorf("failed to unmarshal group %s: %w", entry.Name, err)
				}
				// Merge: overwrite existing group with same name or append
				found := false
				for i, existing := range s3cfg.Groups {
					if existing.Name == g.Name {
						s3cfg.Groups[i] = g
						found = true
						break
					}
				}
				if !found {
					s3cfg.Groups = append(s3cfg.Groups, g)
				}
			}
		}
		return nil
	})
}

func (store *FilerEtcStore) saveGroup(ctx context.Context, group *iam_pb.Group) error {
	if group == nil {
		return fmt.Errorf("group is nil")
	}
	group.Name = strings.TrimSpace(group.Name)
	if group.Name == "" {
		return fmt.Errorf("group name is required")
	}
	return store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		data, err := json.MarshalIndent(group, "", "  ")
		if err != nil {
			return err
		}
		return filer.SaveInsideFiler(context.Background(), client, filer.IamConfigDirectory+"/"+IamGroupsDirectory, group.Name+".json", data)
	})
}

func (store *FilerEtcStore) deleteGroupFile(ctx context.Context, groupName string) error {
	return store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		resp, err := client.DeleteEntry(ctx, &filer_pb.DeleteEntryRequest{
			Directory: filer.IamConfigDirectory + "/" + IamGroupsDirectory,
			Name:      groupName + ".json",
		})
		if err != nil {
			if strings.Contains(err.Error(), filer_pb.ErrNotFound.Error()) {
				return credential.ErrGroupNotFound
			}
			return err
		}
		if resp != nil && resp.Error != "" {
			if strings.Contains(resp.Error, filer_pb.ErrNotFound.Error()) {
				return credential.ErrGroupNotFound
			}
			return fmt.Errorf("delete group %s: %s", groupName, resp.Error)
		}
		return nil
	})
}

func (store *FilerEtcStore) CreateGroup(ctx context.Context, group *iam_pb.Group) error {
	if group != nil {
		group.Name = strings.TrimSpace(group.Name)
	}
	if group == nil || group.Name == "" {
		return fmt.Errorf("group name is required")
	}
	existing, err := store.GetGroup(ctx, group.Name)
	if err != nil {
		if !errors.Is(err, credential.ErrGroupNotFound) {
			return err
		}
	} else if existing != nil {
		return credential.ErrGroupAlreadyExists
	}
	return store.saveGroup(ctx, group)
}

func (store *FilerEtcStore) GetGroup(ctx context.Context, groupName string) (*iam_pb.Group, error) {
	var group *iam_pb.Group
	err := store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		data, err := filer.ReadInsideFiler(ctx, client, filer.IamConfigDirectory+"/"+IamGroupsDirectory, groupName+".json")
		if err != nil {
			if errors.Is(err, filer_pb.ErrNotFound) {
				return credential.ErrGroupNotFound
			}
			return err
		}
		if len(data) == 0 {
			return credential.ErrGroupNotFound
		}
		group = &iam_pb.Group{}
		return json.Unmarshal(data, group)
	})
	return group, err
}

func (store *FilerEtcStore) DeleteGroup(ctx context.Context, groupName string) error {
	if _, err := store.GetGroup(ctx, groupName); err != nil {
		return err
	}
	return store.deleteGroupFile(ctx, groupName)
}

func (store *FilerEtcStore) ListGroups(ctx context.Context) ([]string, error) {
	var names []string
	err := store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		entries, err := listEntries(ctx, client, filer.IamConfigDirectory+"/"+IamGroupsDirectory)
		if err != nil {
			if errors.Is(err, filer_pb.ErrNotFound) {
				return nil
			}
			return err
		}
		for _, entry := range entries {
			if !entry.IsDirectory && strings.HasSuffix(entry.Name, ".json") {
				names = append(names, strings.TrimSuffix(entry.Name, ".json"))
			}
		}
		return nil
	})
	return names, err
}

func (store *FilerEtcStore) UpdateGroup(ctx context.Context, group *iam_pb.Group) error {
	if group != nil {
		group.Name = strings.TrimSpace(group.Name)
	}
	if group == nil || group.Name == "" {
		return fmt.Errorf("group name is required")
	}
	if _, err := store.GetGroup(ctx, group.Name); err != nil {
		return err
	}
	return store.saveGroup(ctx, group)
}
