package entity

import (
	"errors"
	"fmt"
	"strings"
	"time"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/validation"
)

const (
	DefaultTenantServiceAccountName = "tenant-admin"
	DefaultTenantRoleBindingName    = "tenant-admin"
	DefaultTenantClusterRoleName    = "admin"
	DefaultTenantResourceQuotaName  = "tenant-quota"
	MaxTenantKubeconfigTTL          = 2 * time.Hour
)

var (
	ErrInvalidTenantNamespace       = errors.New("invalid tenant namespace")
	ErrInvalidTenantServiceAccount  = errors.New("invalid tenant service account")
	ErrInvalidTenantRoleBinding     = errors.New("invalid tenant role binding")
	ErrInvalidTenantClusterRole     = errors.New("invalid tenant cluster role")
	ErrInvalidTenantResourceQuota   = errors.New("invalid tenant resource quota")
	ErrInvalidTenantKubeconfigToken = errors.New("invalid tenant kubeconfig token")
)

// TenantBinding describes the Kubernetes resources that grant a workspace access
// to one tenant namespace. It intentionally excludes credential material.
type TenantBinding struct {
	Namespace          string
	ServiceAccountName string
	RoleBindingName    string
	ClusterRoleName    string
	ResourceQuotaName  string
	Labels             map[string]string
	Annotations        map[string]string
	ResourceQuotaHard  corev1.ResourceList
}

// TenantKubeconfig contains a short-lived kubeconfig and its expiration time.
// Callers must treat Kubeconfig as secret material and must not persist or log it.
type TenantKubeconfig struct {
	Kubeconfig string
	ExpiresAt  time.Time
}

// NewTenantBinding returns a tenant binding with production-safe default object names.
func NewTenantBinding(namespace string) TenantBinding {
	return TenantBinding{
		Namespace:          namespace,
		ServiceAccountName: DefaultTenantServiceAccountName,
		RoleBindingName:    DefaultTenantRoleBindingName,
		ClusterRoleName:    DefaultTenantClusterRoleName,
		ResourceQuotaName:  DefaultTenantResourceQuotaName,
		Labels: map[string]string{
			"ocdp.io/managed-by": "ocdp",
			"ocdp.io/tenant":     namespace,
		},
	}
}

// WithDefaults fills optional names while preserving explicit caller choices.
func (b TenantBinding) WithDefaults() TenantBinding {
	if b.ServiceAccountName == "" {
		b.ServiceAccountName = DefaultTenantServiceAccountName
	}
	if b.RoleBindingName == "" {
		b.RoleBindingName = DefaultTenantRoleBindingName
	}
	if b.ClusterRoleName == "" {
		b.ClusterRoleName = DefaultTenantClusterRoleName
	}
	if b.ResourceQuotaName == "" {
		b.ResourceQuotaName = DefaultTenantResourceQuotaName
	}
	if b.Labels == nil {
		b.Labels = map[string]string{}
	}
	if b.Labels["ocdp.io/managed-by"] == "" {
		b.Labels["ocdp.io/managed-by"] = "ocdp"
	}
	if b.Namespace != "" && b.Labels["ocdp.io/tenant"] == "" {
		b.Labels["ocdp.io/tenant"] = b.Namespace
	}
	return b
}

// Validate checks the object names required to provision a tenant namespace.
func (b TenantBinding) Validate() error {
	b = b.WithDefaults()
	if strings.TrimSpace(b.Namespace) == "" || len(validation.IsDNS1123Label(b.Namespace)) > 0 {
		return ErrInvalidTenantNamespace
	}
	if strings.TrimSpace(b.ServiceAccountName) == "" || len(validation.IsDNS1123Subdomain(b.ServiceAccountName)) > 0 {
		return ErrInvalidTenantServiceAccount
	}
	if strings.TrimSpace(b.RoleBindingName) == "" || len(validation.IsDNS1123Subdomain(b.RoleBindingName)) > 0 {
		return ErrInvalidTenantRoleBinding
	}
	if strings.TrimSpace(b.ClusterRoleName) == "" || len(validation.IsDNS1123Subdomain(b.ClusterRoleName)) > 0 {
		return ErrInvalidTenantClusterRole
	}
	if strings.TrimSpace(b.ResourceQuotaName) == "" || len(validation.IsDNS1123Subdomain(b.ResourceQuotaName)) > 0 {
		return ErrInvalidTenantResourceQuota
	}
	return nil
}

// TenantTokenTTL caps requested kubeconfig lifetimes at MaxTenantKubeconfigTTL.
func TenantTokenTTL(requested time.Duration) time.Duration {
	if requested <= 0 || requested > MaxTenantKubeconfigTTL {
		return MaxTenantKubeconfigTTL
	}
	return requested
}

func (b TenantBinding) String() string {
	b = b.WithDefaults()
	return fmt.Sprintf("tenant namespace %q serviceAccount %q roleBinding %q", b.Namespace, b.ServiceAccountName, b.RoleBindingName)
}
