package entity

import (
	"crypto/rand"
	"encoding/json"
	"time"

	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"gorm.io/datatypes"
)

// Branch also use (`name`, `main_branch_id`, (IFNULL(`deleted_time`, _utf8mb4'0000-00-00 00:00:00')))
// as unique index, see internal/storage/migrate/record_202303091722.go
type Branch struct {
	ID                   string     `gorm:"primary_key;not null"`
	CreatedAt            time.Time  `gorm:"created_at;type:TIMESTAMP;not null;<-:create"` // create and read only
	UpdatedAt            *time.Time `gorm:"updated_at;type:TIMESTAMP"`
	DeletedTime          *time.Time `gorm:"type:TIMESTAMP;deleted_time;index"`
	CreationFinishedTime *time.Time `gorm:"type:TIMESTAMP;creation_finished_time"`

	TenantID uint64 `gorm:"index;not null"`
	// ProjectID stores the OriginalProjectId (stable, not changed during project migration).
	// It is used for Infra CR interaction only (e.g., namespace, GetBranch, DeleteBranch).
	// DO NOT use this for events, RPC, HTTP responses, or any user-facing APIs.
	// For those cases, always use cluster.ProjectId to get the latest project id.
	ProjectID         uint64 `gorm:"index;not null"`
	CreatedByID       uint64 `gorm:"index;not null"`
	CloudProviderName string `gorm:"not null"`
	RegionName        string `gorm:""`
	Kubernetes        string `gorm:"type:varchar(255);default:''"`

	Name                   string                `gorm:"type:VARCHAR(255);not null"`
	Status                 constant.BranchStatus `gorm:"type:ENUM('creating', 'running', 'deleted', 'restoring','pending');not null"`
	ParentID               string                `gorm:"parent_id;index;not null"`
	MainBranchID           string                `gorm:"main_branch_id;index;not null"`
	EncryptedCloudAdminPwd string                `gorm:"type:VARCHAR(1024);not null;default:''"`
	EncryptedRootPassword  string                `gorm:"type:VARCHAR(1024);not null;default:''"`

	// added in internal/storage/migrate/record_202307192100.go
	CloudAdminPwdSynced bool `gorm:"default:false"`

	// Add per cluster salt for security related works
	Salt []byte `gorm:"type:BINARY(8);default:null"`

	// added in internal/storage/migrate/record_202307031600.go
	HasSetPwd bool `gorm:"default:false"`

	// added in internal/storage/migrate/record_202307121600.go
	Annotations datatypes.JSON `gorm:"type:json"`

	// added in internal/storage/migrate/record_202308241000.go
	CreatedBy string `gorm:"type:VARCHAR(255);not null;default:''"`

	// added in internal/storage/migrate/record_202407261800.go
	ParentTimestamp *time.Time `gorm:"type:TIMESTAMP"`

	CRSpec datatypes.JSON `gorm:"type:json"`
}

type BranchMetadata struct {
	ID          string    `json:"id,omitempty"`
	Name        string    `json:"name,omitempty"`
	TenantID    uint64    `json:"tenant-id,omitempty"`
	ProjectID   uint64    `json:"project-id,omitempty"`
	CreatedAt   time.Time `json:"created-at,omitempty"`
	CreatedByID uint64    `json:"created-by-id,omitempty"`
	Salt        []byte    `json:"salt,omitempty"`
}

func (b *Branch) MetadataOfBranch() *BranchMetadata {
	return &BranchMetadata{
		ID:          b.ID,
		Name:        b.Name,
		TenantID:    b.TenantID,
		ProjectID:   b.ProjectID,
		CreatedAt:   b.CreatedAt,
		CreatedByID: b.CreatedByID,
		Salt:        b.Salt,
	}
}

func (b *Branch) UnmarshalAnnotations() (map[string]string, error) {
	if b.Annotations != nil {
		annotationsMap := make(map[string]string)
		err := json.Unmarshal(b.Annotations, &annotationsMap)
		if err != nil {
			return nil, err
		}
		return annotationsMap, nil
	}
	return nil, nil
}

type BranchStatusCount struct {
	Status constant.BranchStatus
	Count  int
}

func GenerateSalt() ([]byte, error) {
	salt := make([]byte, 8)
	if _, e := rand.Read(salt); e != nil {
		return nil, e
	}
	return salt, nil
}
