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/glog"
	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
	"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
)

func validateServiceAccountId(id string) error {
	return credential.ValidateServiceAccountId(id)
}

func (store *FilerEtcStore) loadServiceAccountsFromMultiFile(ctx context.Context, s3cfg *iam_pb.S3ApiConfiguration) error {
	return store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		dir := filer.IamConfigDirectory + "/" + IamServiceAccountsDirectory
		entries, err := listEntries(ctx, client, dir)
		if err != nil {
			if 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 {
					glog.Warningf("Failed to read service account file %s: %v", entry.Name, err)
					continue
				}
				content = c
			}

			if len(content) > 0 {
				sa := &iam_pb.ServiceAccount{}
				if err := json.Unmarshal(content, sa); err != nil {
					glog.Warningf("Failed to unmarshal service account %s: %v", entry.Name, err)
					continue
				}
				s3cfg.ServiceAccounts = append(s3cfg.ServiceAccounts, sa)
			}
		}
		return nil
	})
}

func (store *FilerEtcStore) saveServiceAccount(ctx context.Context, sa *iam_pb.ServiceAccount) error {
	if sa == nil {
		return fmt.Errorf("service account is nil")
	}
	if err := validateServiceAccountId(sa.Id); err != nil {
		return err
	}
	return store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		data, err := json.MarshalIndent(sa, "", "  ")
		if err != nil {
			return err
		}
		return filer.SaveInsideFiler(context.Background(), client, filer.IamConfigDirectory+"/"+IamServiceAccountsDirectory, sa.Id+".json", data)
	})
}

func (store *FilerEtcStore) deleteServiceAccount(ctx context.Context, saId string) error {
	if err := validateServiceAccountId(saId); err != nil {
		return err
	}
	return store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		resp, err := client.DeleteEntry(ctx, &filer_pb.DeleteEntryRequest{
			Directory: filer.IamConfigDirectory + "/" + IamServiceAccountsDirectory,
			Name:      saId + ".json",
		})
		if err != nil {
			if strings.Contains(err.Error(), filer_pb.ErrNotFound.Error()) {
				return credential.ErrServiceAccountNotFound
			}
			return err
		}
		if resp != nil && resp.Error != "" {
			if strings.Contains(resp.Error, filer_pb.ErrNotFound.Error()) {
				return credential.ErrServiceAccountNotFound
			}
			return fmt.Errorf("delete service account %s: %s", saId, resp.Error)
		}
		return nil
	})
}

func (store *FilerEtcStore) CreateServiceAccount(ctx context.Context, sa *iam_pb.ServiceAccount) error {
	existing, err := store.GetServiceAccount(ctx, sa.Id)
	if err != nil {
		if !errors.Is(err, credential.ErrServiceAccountNotFound) {
			return err
		}
	} else if existing != nil {
		return fmt.Errorf("service account %s already exists", sa.Id)
	}
	return store.saveServiceAccount(ctx, sa)
}

func (store *FilerEtcStore) UpdateServiceAccount(ctx context.Context, id string, sa *iam_pb.ServiceAccount) error {
	if sa.Id != id {
		return fmt.Errorf("service account ID mismatch")
	}
	_, err := store.GetServiceAccount(ctx, id)
	if err != nil {
		return err
	}
	return store.saveServiceAccount(ctx, sa)
}

func (store *FilerEtcStore) DeleteServiceAccount(ctx context.Context, id string) error {
	return store.deleteServiceAccount(ctx, id)
}

func (store *FilerEtcStore) GetServiceAccount(ctx context.Context, id string) (*iam_pb.ServiceAccount, error) {
	if err := validateServiceAccountId(id); err != nil {
		return nil, err
	}
	var sa *iam_pb.ServiceAccount
	err := store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		data, err := filer.ReadInsideFiler(ctx, client, filer.IamConfigDirectory+"/"+IamServiceAccountsDirectory, id+".json")
		if err != nil {
			if err == filer_pb.ErrNotFound {
				return credential.ErrServiceAccountNotFound
			}
			return err
		}
		if len(data) == 0 {
			return credential.ErrServiceAccountNotFound
		}
		sa = &iam_pb.ServiceAccount{}
		return json.Unmarshal(data, sa)
	})
	return sa, err
}

func (store *FilerEtcStore) ListServiceAccounts(ctx context.Context) ([]*iam_pb.ServiceAccount, error) {
	var accounts []*iam_pb.ServiceAccount
	err := store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		dir := filer.IamConfigDirectory + "/" + IamServiceAccountsDirectory
		entries, err := listEntries(ctx, client, dir)
		if err != nil {
			if 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 {
					glog.Warningf("Failed to read service account file %s: %v", entry.Name, err)
					continue
				}
				content = c
			}

			if len(content) > 0 {
				sa := &iam_pb.ServiceAccount{}
				if err := json.Unmarshal(content, sa); err != nil {
					glog.Warningf("Failed to unmarshal service account %s: %v", entry.Name, err)
					continue
				}
				accounts = append(accounts, sa)
			}
		}
		return nil
	})
	return accounts, err
}

func (store *FilerEtcStore) GetServiceAccountByAccessKey(ctx context.Context, accessKey string) (*iam_pb.ServiceAccount, error) {
	accounts, err := store.ListServiceAccounts(ctx)
	if err != nil {
		return nil, err
	}
	for _, sa := range accounts {
		if sa.Credential != nil && sa.Credential.AccessKey == accessKey {
			return sa, nil
		}
	}
	return nil, credential.ErrAccessKeyNotFound
}
