package service

import (
	"context"
	"strings"

	"github.com/keys-pub/keys"
	"github.com/keys-pub/keys-ext/vault/secrets"
	"github.com/keys-pub/keys/tsutil"
	"github.com/pkg/errors"
)

func (s *service) Secret(ctx context.Context, req *SecretRequest) (*SecretResponse, error) {
	secret, err := secrets.Get(s.vault, req.ID)
	if err != nil {
		return nil, err
	}
	if secret == nil {
		return nil, keys.NewErrNotFound(req.ID)
	}
	return &SecretResponse{
		Secret: secretToRPC(secret),
	}, nil
}

func (s *service) SecretSave(ctx context.Context, req *SecretSaveRequest) (*SecretSaveResponse, error) {
	secret := secretFromRPC(req.Secret)
	if secret.Type == secrets.UnknownType {
		return nil, errors.Errorf("unknown secret type")
	}

	name := strings.TrimSpace(secret.Name)
	if name == "" {
		return nil, errors.Errorf("no name specified")
	}

	out, _, err := secrets.Save(s.vault, secret)
	if err != nil {
		return nil, err
	}

	return &SecretSaveResponse{
		Secret: secretToRPC(out),
	}, nil
}

func (s *service) SecretRemove(ctx context.Context, req *SecretRemoveRequest) (*SecretRemoveResponse, error) {
	if req.ID == "" {
		return nil, errors.Errorf("id not specified")
	}
	// TODO: What if ID isn't for secret?
	ok, err := s.vault.Delete(req.ID)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, keys.NewErrNotFound(req.ID)
	}
	return &SecretRemoveResponse{}, nil
}

func (s *service) Secrets(ctx context.Context, req *SecretsRequest) (*SecretsResponse, error) {
	if req.SortField == "" {
		req.SortField = "name"
	}

	secrets, err := secrets.List(s.vault,
		secrets.WithQuery(req.Query),
		secrets.WithSort(req.SortField),
		secrets.WithSortDirection(sortDirectionToVault(req.SortDirection)),
	)
	if err != nil {
		return nil, err
	}
	out := secretsToRPC(secrets)

	return &SecretsResponse{
		Secrets:       out,
		SortField:     req.SortField,
		SortDirection: req.SortDirection,
	}, nil
}

func sortDirectionToVault(d SortDirection) secrets.SortDirection {
	switch d {
	case SortAsc:
		return secrets.Ascending
	case SortDesc:
		return secrets.Descending
	default:
		return secrets.Ascending
	}
}

func secretsToRPC(ss []*secrets.Secret) []*Secret {
	out := make([]*Secret, 0, len(ss))
	for _, s := range ss {
		out = append(out, secretToRPC(s))
	}
	return out
}

func secretToRPC(s *secrets.Secret) *Secret {
	return &Secret{
		ID:        s.ID,
		Name:      s.Name,
		Type:      secretTypeToRPC(s.Type),
		Username:  s.Username,
		Password:  s.Password,
		URL:       s.URL,
		Notes:     s.Notes,
		CreatedAt: tsutil.Millis(s.CreatedAt),
		UpdatedAt: tsutil.Millis(s.UpdatedAt),
	}
}

func secretFromRPC(s *Secret) *secrets.Secret {
	return &secrets.Secret{
		ID:        s.ID,
		Name:      s.Name,
		Type:      secretTypeFromRPC(s.Type),
		Username:  s.Username,
		Password:  s.Password,
		URL:       s.URL,
		Notes:     s.Notes,
		CreatedAt: tsutil.ParseMillis(s.CreatedAt),
		UpdatedAt: tsutil.ParseMillis(s.UpdatedAt),
	}
}

func secretTypeToRPC(t secrets.Type) SecretType {
	switch t {
	case secrets.PasswordType:
		return PasswordSecret
	case secrets.NoteType:
		return NoteSecret
	default:
		return UnknownSecret
	}
}

func secretTypeFromRPC(s SecretType) secrets.Type {
	switch s {
	case PasswordSecret:
		return secrets.PasswordType
	case NoteSecret:
		return secrets.NoteType
	default:
		return secrets.UnknownType
	}
}
