package service

import (
	"context"
	"time"

	"github.com/micro/go-micro/v2/auth"
	pb "github.com/micro/go-micro/v2/auth/service/proto"
	"github.com/micro/go-micro/v2/client"
)

// NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth {
	svc := new(svc)
	svc.Init(opts...)
	return svc
}

// svc is the service implementation of the Auth interface
type svc struct {
	options auth.Options
	auth    pb.AuthService
}

func (s *svc) String() string {
	return "service"
}

func (s *svc) Init(opts ...auth.Option) error {
	for _, o := range opts {
		o(&s.options)
	}

	dc := client.DefaultClient
	s.auth = pb.NewAuthService("go.micro.auth", dc)

	return nil
}

// Generate a new auth account
func (s *svc) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
	// construct the request
	options := auth.NewGenerateOptions(opts...)
	sa := &auth.Account{
		Id:       id,
		Roles:    options.Roles,
		Metadata: options.Metadata,
	}
	req := &pb.GenerateRequest{Account: serializeAccount(sa)}

	// execute the request
	resp, err := s.auth.Generate(context.Background(), req)
	if err != nil {
		return nil, err
	}

	// format the response
	return deserializeAccount(resp.Account), nil
}

// Revoke an authorization account
func (s *svc) Revoke(token string) error {
	// contruct the request
	req := &pb.RevokeRequest{Token: token}

	// execute the request
	_, err := s.auth.Revoke(context.Background(), req)
	return err
}

// Validate an account token
func (s *svc) Validate(token string) (*auth.Account, error) {
	resp, err := s.auth.Validate(context.Background(), &pb.ValidateRequest{Token: token})
	if err != nil {
		return nil, err
	}

	return deserializeAccount(resp.Account), nil
}

func serializeAccount(sa *auth.Account) *pb.Account {
	roles := make([]*pb.Role, len(sa.Roles))
	for i, r := range sa.Roles {
		roles[i] = &pb.Role{
			Name: r.Name,
		}

		if r.Resource != nil {
			roles[i].Resource = &pb.Resource{
				Name: r.Resource.Name,
				Type: r.Resource.Type,
			}
		}
	}

	return &pb.Account{
		Id:       sa.Id,
		Roles:    roles,
		Metadata: sa.Metadata,
	}
}

func deserializeAccount(a *pb.Account) *auth.Account {
	// format the response
	sa := &auth.Account{
		Id:       a.Id,
		Token:    a.Token,
		Created:  time.Unix(a.Created, 0),
		Expiry:   time.Unix(a.Expiry, 0),
		Metadata: a.Metadata,
	}

	sa.Roles = make([]*auth.Role, len(a.Roles))
	for i, r := range a.Roles {
		sa.Roles[i] = &auth.Role{
			Name: r.Name,
		}

		if r.Resource != nil {
			sa.Roles[i].Resource = &auth.Resource{
				Name: r.Resource.Name,
				Type: r.Resource.Type,
			}
		}
	}

	return sa
}