// Licensed to the 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.
// The 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 jobservices handles services necessary WRT handling jobs from
// SDKs. Nominally this is the entry point for most users, and a job's
// external interactions outside of pipeline execution.
//
// This includes handling receiving, staging, and provisioning artifacts,
// and orchestrating external workers, such as for loopback mode.
//
// Execution of jobs is abstracted away to an execute function specified
// at server construction time.
package jobservices

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"sync/atomic"

	fnpb "github.com/apache/beam/sdks/v2/go/pkg/beam/model/fnexecution_v1"
	jobpb "github.com/apache/beam/sdks/v2/go/pkg/beam/model/jobmanagement_v1"
	pipepb "github.com/apache/beam/sdks/v2/go/pkg/beam/model/pipeline_v1"
	"github.com/apache/beam/sdks/v2/go/pkg/beam/runners/prism/internal/urns"
	"golang.org/x/exp/slog"
	"google.golang.org/protobuf/types/known/structpb"
)

var capabilities = map[string]struct{}{
	urns.RequirementSplittableDoFn: {},
}

// TODO, move back to main package, and key off of executor handlers?
// Accept whole pipeline instead, and look at every PTransform too.
func isSupported(requirements []string) error {
	var unsupported []string
	for _, req := range requirements {
		if _, ok := capabilities[req]; !ok {
			unsupported = append(unsupported, req)
		}
	}
	if len(unsupported) > 0 {
		sort.Strings(unsupported)
		return fmt.Errorf("local runner doesn't support the following required features: %v", strings.Join(unsupported, ","))
	}
	return nil
}

// Job is an interface to the job services for executing pipelines.
// It allows the executor to communicate status, messages, and metrics
// back to callers of the Job Management API.
type Job struct {
	key     string
	jobName string

	Pipeline *pipepb.Pipeline
	options  *structpb.Struct

	// Management side concerns.
	msgChan   chan string
	state     atomic.Value // jobpb.JobState_Enum
	stateChan chan jobpb.JobState_Enum

	// Context used to terminate this job.
	RootCtx  context.Context
	CancelFn context.CancelFunc

	metrics metricsStore
}

func (j *Job) ContributeMetrics(payloads *fnpb.ProcessBundleResponse) {
	j.metrics.ContributeMetrics(payloads)
}

func (j *Job) String() string {
	return fmt.Sprintf("%v[%v]", j.key, j.jobName)
}

func (j *Job) LogValue() slog.Value {
	return slog.GroupValue(
		slog.String("key", j.key),
		slog.String("name", j.jobName))
}

func (j *Job) SendMsg(msg string) {
	j.msgChan <- msg
}

// Start indicates that the job is preparing to execute.
func (j *Job) Start() {
	j.stateChan <- jobpb.JobState_STARTING
}

// Running indicates that the job is executing.
func (j *Job) Running() {
	j.stateChan <- jobpb.JobState_RUNNING
}

// Done indicates that the job completed successfully.
func (j *Job) Done() {
	j.stateChan <- jobpb.JobState_DONE
}

// Failed indicates that the job completed unsuccessfully.
func (j *Job) Failed() {
	j.stateChan <- jobpb.JobState_FAILED
}
