// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package helpers

import (
	"time"

	"github.com/onsi/gomega"
	grpclib "google.golang.org/grpc"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/timestamppb"
	"sigs.k8s.io/yaml"

	modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1"
	"github.com/apache/skywalking-banyandb/pkg/fs/remote"
)

// Test deployment modes carried in SharedContext.Mode.
const (
	// ModeStandalone marks a single-process integration suite where the liaison
	// and data node share an in-process schema cache.
	ModeStandalone = "standalone"
	// ModeDistributed marks a multi-process integration suite where the liaison
	// and data nodes have separate schema caches.
	ModeDistributed = "distributed"
)

// SharedContext is the context shared between test cases in the integration testing.
//
//nolint:govet // fieldalignment cannot reduce the pointer-byte run further; readability beats the 8-byte saving for a test-only struct.
type SharedContext struct {
	BaseTime   time.Time
	Connection *grpclib.ClientConn
	// DataNodeAddrs lists the gRPC addresses of the cluster's data nodes.
	// Populated by the distributed BeforeSuite; nil for standalone runs.
	// Cluster-only specs ( partial-cluster barrier scenarios) pass
	// entries from this slice to setup.PauseDataNodeWatch /
	// ResumeDataNodeWatch to drive a single node out of sync while the
	// rest of the cluster stays caught up.
	DataNodeAddrs []string
	// LiaisonAddr is the receiving liaison's gRPC address. Populated by
	// the distributed BeforeSuite; empty for standalone runs. Specs use
	// it with setup.PauseDataNodeWatch when they need to pause the
	// receiving liaison's own SchemaRegistry — the cluster barrier's
	// selfName probe reads through this SR, so pausing it surfaces a
	// laggard via the public AwaitX RPCs without needing
	// NodeSchemaStatusService exposed on data-node ports.
	LiaisonAddr string
	Mode        string
}

// Args is a wrapper seals all necessary info for table specs.
type Args struct {
	Begin           *timestamppb.Timestamp
	End             *timestamppb.Timestamp
	Input           string
	Want            string
	Stages          []string
	Offset          time.Duration
	Duration        time.Duration
	WantEmpty       bool
	WantErr         bool
	DisOrder        bool
	IgnoreElementID bool
}

// UnmarshalYAML decodes YAML raw bytes to proto.Message.
func UnmarshalYAML(ii []byte, m proto.Message) {
	j, err := yaml.YAMLToJSON(ii)
	gomega.Expect(err).NotTo(gomega.HaveOccurred())
	gomega.Expect(protojson.Unmarshal(j, m)).To(gomega.Succeed())
}

// TimeRange returns a modelv1.TimeRange based on Args and SharedContext.
func TimeRange(args Args, shardContext SharedContext) *modelv1.TimeRange {
	if args.Begin != nil && args.End != nil {
		return &modelv1.TimeRange{
			Begin: args.Begin,
			End:   args.End,
		}
	}
	b := shardContext.BaseTime.Add(args.Offset)
	return &modelv1.TimeRange{
		Begin: timestamppb.New(b),
		End:   timestamppb.New(b.Add(args.Duration)),
	}
}

// BackupSharedContext is the context shared between test cases in the snapshot testing.
type BackupSharedContext struct {
	DataAddr   string
	Connection *grpclib.ClientConn
	FS         remote.FS
	RootDir    string
	DestDir    string
	DestURL    string
	S3Args     []string
}

// LifecycleSharedContext is the context shared between test cases in the lifecycle testing.
type LifecycleSharedContext struct {
	BaseTime    time.Time
	Connection  *grpclib.ClientConn
	LiaisonAddr string
	DataAddr    string
	// DataHTTPURL is the hot data node's Prometheus /metrics endpoint. Used
	// by sender-label verifications that need to scrape banyandb_queue_sub_*
	// directly from the data node.
	DataHTTPURL string
	// WarmHTTPURL is the warm data node's Prometheus /metrics endpoint. Both
	// data nodes (hot and warm) see migration writes from the lifecycle
	// publisher, so either endpoint can carry the sender labels.
	WarmHTTPURL   string
	SrcDir        string
	DestDir       string
	MetadataFlags []string
}
