package client

import (
	"context"
	"fmt"

	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/control"
	controlSvc "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/control/server"
	"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

type Client struct {
	svc control.ControlServiceClient
	key *keys.PrivateKey
}

type Config struct {
	Logger *zap.Logger
}

type PolicyData struct {
	Namespace string
	Chain     *chain.Chain
}

type PolicyInfo struct {
	Namespace string
	ChainID   chain.ID
}

func New(ctx context.Context, addr string, key *keys.PrivateKey) (*Client, error) {
	conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		return nil, fmt.Errorf("failed to dial s3 gw control api: %w", err)
	}

	svc := control.NewControlServiceClient(conn)

	cli := &Client{
		svc: svc,
		key: key,
	}

	return cli, cli.Healthcheck(ctx)
}

func (c *Client) Healthcheck(ctx context.Context) error {
	req := &control.HealthCheckRequest{}
	if err := controlSvc.SignMessage(&c.key.PrivateKey, req); err != nil {
		return err
	}

	res, err := c.svc.HealthCheck(ctx, req)
	if err != nil {
		return err
	}

	if res.Body.HealthStatus != control.HealthStatus_READY {
		return fmt.Errorf("service isn't ready, status: %s", res.Body.HealthStatus)
	}

	return nil
}

func (c *Client) PutPolicies(ctx context.Context, policies []PolicyData) error {
	chainDatas := make([]*control.PutPoliciesRequest_ChainData, len(policies))
	for i := range policies {
		chainDatas[i] = &control.PutPoliciesRequest_ChainData{
			Namespace: policies[i].Namespace,
			Chain:     policies[i].Chain.Bytes(),
		}
	}

	req := &control.PutPoliciesRequest{
		Body: &control.PutPoliciesRequest_Body{
			ChainDatas: chainDatas,
		},
	}

	if err := controlSvc.SignMessage(&c.key.PrivateKey, req); err != nil {
		return err
	}

	_, err := c.svc.PutPolicies(ctx, req)
	return err
}

func (c *Client) RemovePolicies(ctx context.Context, policies []PolicyInfo) error {
	chainInfos := make([]*control.RemovePoliciesRequest_ChainInfo, len(policies))
	for i := range policies {
		chainInfos[i] = &control.RemovePoliciesRequest_ChainInfo{
			Namespace: policies[i].Namespace,
			ChainID:   []byte(policies[i].ChainID),
		}
	}

	req := &control.RemovePoliciesRequest{
		Body: &control.RemovePoliciesRequest_Body{
			ChainInfos: chainInfos,
		},
	}

	if err := controlSvc.SignMessage(&c.key.PrivateKey, req); err != nil {
		return err
	}

	_, err := c.svc.RemovePolicies(ctx, req)
	return err
}

func (c *Client) GetPolicy(ctx context.Context, namespace string, chainID chain.ID) (*chain.Chain, error) {
	req := &control.GetPolicyRequest{
		Body: &control.GetPolicyRequest_Body{
			Namespace: namespace,
			ChainID:   []byte(chainID),
		},
	}

	if err := controlSvc.SignMessage(&c.key.PrivateKey, req); err != nil {
		return nil, err
	}

	resp, err := c.svc.GetPolicy(ctx, req)
	if err != nil {
		return nil, err
	}

	var policyChain chain.Chain
	if err = policyChain.DecodeBytes(resp.GetBody().GetChain()); err != nil {
		return nil, err
	}

	return &policyChain, nil
}

func (c *Client) ListPolicies(ctx context.Context, namespace string) ([]chain.ID, error) {
	req := &control.ListPoliciesRequest{
		Body: &control.ListPoliciesRequest_Body{
			Namespace: namespace,
		},
	}

	if err := controlSvc.SignMessage(&c.key.PrivateKey, req); err != nil {
		return nil, err
	}

	resp, err := c.svc.ListPolicies(ctx, req)
	if err != nil {
		return nil, err
	}

	res := make([]chain.ID, len(resp.GetBody().GetChainIDs()))
	for i, chainID := range resp.GetBody().GetChainIDs() {
		res[i] = chain.ID(chainID)
	}

	return res, nil
}