/*
Copyright 2024.

Licensed 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 task

import (
	"fmt"

	o11yv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/constants"
)

// Identity is the normalized, scope-aware identity of an IntegrationTask.
// Downstream tasks (IAMRole, ConfigMap, Deployment) consume this instead of
// reading spec.ClusterID directly, so tenant-scoped tasks are not forced through
// cluster-shaped assumptions.
type Identity struct {
	Scope         o11yv1.TaskScope
	TenantID      string
	BizType       string
	Region        string
	Namespace     string
	TaskType      o11yv1.TaskType
	AccessRoleARN string
	Alias         string
	ProjectID     string
	// BucketSharePolicy is the task-wide destination bucket isolation policy.
	// Empty resolves to exclusive where needed.
	BucketSharePolicy o11yv1.BucketSharePolicy
	// ClusterID is only meaningful for cluster-scoped tasks.
	ClusterID string
}

// IsTenant reports whether the identity is tenant-scoped.
func (id Identity) IsTenant() bool {
	return id.Scope == o11yv1.ScopeTenant
}

// ResourceSuffix returns the naming suffix for scope-derived resources
// (ConfigMap / Deployment / ServiceAccount). Cluster scope keeps the historical
// per-tenant suffix so existing pipelines are untouched; tenant scope adds the
// data type so each (tenant, dataType) pipeline is isolated.
func (id Identity) ResourceSuffix() string {
	if id.IsTenant() {
		return fmt.Sprintf("%s-%s", id.TenantID, id.TaskType)
	}
	return id.TenantID
}

// Identity computes and validates the normalized identity for the task. Region is
// taken from the controller's ServiceInfo (each regional server serves a single
// region), so tenant scope does not need a region field on the spec.
func (c *Context) Identity() (Identity, error) {
	scope := c.Task.Spec.Scope
	if scope == "" {
		scope = o11yv1.ScopeCluster
	}

	id := Identity{
		Scope:             scope,
		TenantID:          c.Task.Labels[constants.LabelTenantID],
		BizType:           c.Task.Labels[constants.LabelBizType],
		Region:            c.ServiceInfo.Region,
		Namespace:         c.Task.Namespace,
		TaskType:          c.Task.Spec.TaskType,
		AccessRoleARN:     c.Task.Spec.AccessRoleARN,
		Alias:             c.Task.Labels[constants.LabelAlias],
		ProjectID:         c.Task.Labels[constants.LabelProjectID],
		BucketSharePolicy: c.Task.Spec.BucketSharePolicy,
		ClusterID:         c.Task.Spec.ClusterID,
	}

	switch scope {
	case o11yv1.ScopeCluster:
		// Cluster scope keeps existing behavior; no extra required fields here.
	case o11yv1.ScopeTenant:
		if id.TenantID == "" {
			return Identity{}, fmt.Errorf("tenant-scoped integration task requires tenant id label")
		}
		if id.BizType == "" {
			return Identity{}, fmt.Errorf("tenant-scoped integration task requires biz type label")
		}
		if id.TaskType == "" {
			return Identity{}, fmt.Errorf("tenant-scoped integration task requires task type")
		}
	default:
		return Identity{}, fmt.Errorf("unrecognized integration task scope: %s", scope)
	}

	return id, nil
}

// ValidateCreatable reports whether this identity may be provisioned. It is
// enforced only on the create/update path (never on delete) so an already-
// invalid resource can still be cleaned up. All current OpenAPI tiers are
// creatable; tier-specific behavior is handled by the downstream deployType
// and source-prefix config.
func (id Identity) ValidateCreatable() error {
	if !id.BucketSharePolicy.IsValid() {
		return fmt.Errorf("unrecognized bucket share policy: %s", id.BucketSharePolicy)
	}
	return nil
}
