package extension

import (
	"context"
	"strings"

	"git.perx.ru/perxis/perxis-go/pkg/action"
	"git.perx.ru/perxis/perxis-go/pkg/errors"
	"git.perx.ru/perxis/perxis-go/pkg/operation"
	pb "git.perx.ru/perxis/perxis-go/proto/extensions"
	"google.golang.org/protobuf/proto"
)

type Server struct {
	extensions map[string]Extension
	operations operation.Service
	pb.UnimplementedExtensionServiceServer
}

func NewServer(operation operation.Service, extensions ...Extension) *Server {
	srv := &Server{
		extensions: make(map[string]Extension, len(extensions)),
		// todo: нужно как-то неявно создавать и регистрировать сервер операций
		operations: operation,
	}
	for _, s := range extensions {
		srv.extensions[s.GetDescriptor().Extension] = s
	}
	return srv
}

func (s *Server) getExtensions(_ context.Context, extensions []string) ([]Extension, error) {
	var res []Extension
	for _, ext := range extensions {
		e, ok := s.extensions[ext]
		if !ok {
			return nil, errors.Wrap(ErrUnknownExtension, ext)
		}

		res = append(res, e)
	}
	return res, nil
}

func (s *Server) Install(ctx context.Context, req *InstallRequest) (*operation.Proto, error) {
	exts, err := s.getExtensions(ctx, req.Extensions)
	if err != nil {
		return nil, err
	}

	desc := "Install extensions " + strings.Join(req.Extensions, ", ")
	op, err := s.operations.Create(ctx, desc,
		func(ctx context.Context) (proto.Message, error) {
			for _, ext := range exts {
				if err := ext.Install(ctx, req); err != nil {
					return nil, errors.Wrap(err, ext.GetDescriptor().Extension)
				}
			}
			return nil, nil
		})

	return op.Proto(), err
}

func (s *Server) Uninstall(ctx context.Context, req *UninstallRequest) (*operation.Proto, error) {
	exts, err := s.getExtensions(ctx, req.Extensions)
	if err != nil {
		return nil, err
	}

	desc := "Uninstall extensions " + strings.Join(req.Extensions, ", ")
	op, err := s.operations.Create(ctx, desc,
		func(ctx context.Context) (proto.Message, error) {
			for _, ext := range exts {
				if err := ext.Uninstall(ctx, req); err != nil {
					return nil, errors.Wrap(err, ext.GetDescriptor().Extension)
				}
			}
			return nil, nil
		})

	return op.Proto(), err
}

func (s *Server) Check(ctx context.Context, req *CheckRequest) (*operation.Proto, error) {
	exts, err := s.getExtensions(ctx, req.Extensions)
	if err != nil {
		return nil, err
	}

	desc := "Check extensions " + strings.Join(req.Extensions, ", ")
	op, err := s.operations.Create(ctx, desc,
		func(ctx context.Context) (proto.Message, error) {
			for _, ext := range exts {
				if err := ext.Check(ctx, req); err != nil {
					return nil, errors.Wrap(err, ext.GetDescriptor().Extension)
				}
			}
			return nil, nil
		})

	return op.Proto(), err
}

func (s *Server) Action(ctx context.Context, in *pb.ActionRequest) (*pb.ActionResponse, error) {
	actionURL, err := action.NewURL(in.Action)
	if err != nil {
		return nil, err
	}
	ext := actionURL.Extension()
	if ext == "" {
		ext = in.Extension
	}
	if ext == "" {
		return nil, errors.New("extension ID required")
	}

	svc, ok := s.extensions[in.Extension]
	if !ok {
		return nil, ErrUnknownExtension
	}

	out, err := svc.Action(ctx, in)

	if out == nil {
		out = &ActionResponse{}
	}

	if err != nil {
		out.State = ResponseError
		out.Error = err.Error()
		out.Msg += errors.GetDetail(err)
	}

	return out, nil
}

func (s *Server) Start() error {
	var errs []error
	for _, svc := range s.extensions {
		if r, ok := svc.(Runnable); ok {
			if err := r.Start(); err != nil {
				errs = append(errs, err)
			}
		}
	}

	if len(errs) > 0 {
		return errors.WithErrors(ErrStart, errs...)
	}

	return nil
}

func (s *Server) Stop() error {
	var errs []error
	for _, svc := range s.extensions {
		if r, ok := svc.(Runnable); ok {
			if err := r.Stop(); err != nil {
				errs = append(errs, err)
			}
		}
	}

	if len(errs) > 0 {
		return errors.WithErrors(ErrStop, errs...)
	}

	return nil
}

// --------------
// Попытки сделать один сервер для расширений и для менеджера
// --------------

type ExtensionsGetter interface {
	GetInstalledExtensions(ctx context.Context, extensions ...string) ([]Extension, error)
}

func NewMultiExtensionsGetter(svc ...Extension) ExtensionsGetter {
	g := &multiExtensionsGetter{
		extensions: make(map[string]Extension, len(svc)),
	}
	for _, s := range svc {
		g.extensions[s.GetDescriptor().Extension] = s
	}
	return g
}

type multiExtensionsGetter struct {
	extensions map[string]Extension
}

func (g *multiExtensionsGetter) GetInstalledExtensions(_ context.Context, extensions ...string) ([]Extension, error) {
	var res []Extension
	if len(extensions) == 0 {
		for _, e := range g.extensions {
			res = append(res, e)
		}
		return res, nil
	}

	for _, ext := range extensions {
		e, ok := g.extensions[ext]
		if !ok {
			return nil, errors.Wrap(ErrUnknownExtension, ext)
		}

		res = append(res, e)
	}
	return res, nil
}

func NewMonoExtensionsGetter(svc Extension) ExtensionsGetter {
	return &monoExtensionsGetter{extension: svc}
}

type monoExtensionsGetter struct {
	extension Extension
}

func (g *monoExtensionsGetter) GetInstalledExtensions(_ context.Context, _ ...string) ([]Extension, error) {
	return []Extension{g.extension}, nil
}