// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later

package issues

import (
	"context"

	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)

func init() {
	db.RegisterModel(new(CustomFieldDef))
	db.RegisterModel(new(CustomFieldValue))
}

// CustomFieldType represents the data type of a custom field.
type CustomFieldType string

const (
	CustomFieldTypeText     CustomFieldType = "text"
	CustomFieldTypeNumber   CustomFieldType = "number"
	CustomFieldTypeDate     CustomFieldType = "date"
	CustomFieldTypeDropdown CustomFieldType = "dropdown"
	CustomFieldTypeCheckbox CustomFieldType = "checkbox"
	CustomFieldTypeURL      CustomFieldType = "url"
)

// CustomFieldScope determines where the field appears.
type CustomFieldScope string

const (
	CustomFieldScopeIssue CustomFieldScope = "issue" // appears in issue sidebar
	CustomFieldScopeRepo  CustomFieldScope = "repo"  // appears in repo settings metadata
)

// CustomFieldDef defines a custom field at the org level.
// owner_id = org ID, scope = issue or repo.
// repo_id is kept for backward compat but 0 for org-level definitions.
type CustomFieldDef struct {
	ID          int64              `xorm:"pk autoincr"`
	OwnerID     int64              `xorm:"INDEX NOT NULL DEFAULT 0 'owner_id'"` // org that owns this field
	RepoID      int64              `xorm:"INDEX NOT NULL DEFAULT 0 'repo_id'"`  // 0 = org-level (inherited by all repos)
	Scope       CustomFieldScope   `xorm:"VARCHAR(10) NOT NULL DEFAULT 'issue' 'scope'"`
	Name        string             `xorm:"NOT NULL"`
	FieldType   CustomFieldType    `xorm:"VARCHAR(20) NOT NULL 'field_type'"`
	Description string             `xorm:"TEXT"`
	Options     string             `xorm:"TEXT"` // JSON array for dropdown options
	Required    bool               `xorm:"NOT NULL DEFAULT false"`
	SortOrder   int                `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
	IsActive    bool               `xorm:"NOT NULL DEFAULT true 'is_active'"`
	CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
	UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}

func (CustomFieldDef) TableName() string {
	return "custom_field_def"
}

// CustomFieldValue stores a custom field value for an entity (issue or repo).
type CustomFieldValue struct {
	ID          int64              `xorm:"pk autoincr"`
	EntityID    int64              `xorm:"INDEX NOT NULL 'entity_id'"`   // issue ID or repo ID
	EntityType  string             `xorm:"VARCHAR(10) NOT NULL DEFAULT 'issue' 'entity_type'"` // "issue" or "repo"
	FieldID     int64              `xorm:"INDEX NOT NULL 'field_id'"`
	Value       string             `xorm:"TEXT"`
	CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
	UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}

func (CustomFieldValue) TableName() string {
	return "custom_field_value"
}

// ──────────────────────────────────────────────────────────────────────
// Queries for org-level field definitions
// ──────────────────────────────────────────────────────────────────────

// GetCustomFieldsByOwner returns all active field definitions for an org with a given scope.
func GetCustomFieldsByOwner(ctx context.Context, ownerID int64, scope CustomFieldScope) ([]*CustomFieldDef, error) {
	fields := make([]*CustomFieldDef, 0, 10)
	return fields, db.GetEngine(ctx).
		Where("owner_id = ? AND scope = ? AND is_active = ?", ownerID, scope, true).
		OrderBy("sort_order ASC, id ASC").
		Find(&fields)
}

// GetAllCustomFieldsByOwner returns all field definitions for an org (including inactive).
func GetAllCustomFieldsByOwner(ctx context.Context, ownerID int64) ([]*CustomFieldDef, error) {
	fields := make([]*CustomFieldDef, 0, 10)
	return fields, db.GetEngine(ctx).
		Where("owner_id = ?", ownerID).
		OrderBy("scope ASC, sort_order ASC, id ASC").
		Find(&fields)
}

// GetCustomFieldsByOwnerAndScope returns all fields for an org filtered by scope.
func GetCustomFieldsByOwnerAndScope(ctx context.Context, ownerID int64, scope CustomFieldScope) ([]*CustomFieldDef, error) {
	fields := make([]*CustomFieldDef, 0, 10)
	return fields, db.GetEngine(ctx).
		Where("owner_id = ? AND scope = ?", ownerID, scope).
		OrderBy("sort_order ASC, id ASC").
		Find(&fields)
}

// ──────────────────────────────────────────────────────────────────────
// Backward-compatible queries (load by repo's owner)
// ──────────────────────────────────────────────────────────────────────

// GetCustomFieldsByRepo returns active issue-scoped fields for a repo's org.
// This is the main query used by the issue sidebar.
func GetCustomFieldsByRepo(ctx context.Context, repoID int64) ([]*CustomFieldDef, error) {
	// First try org-level fields (owner_id != 0, repo_id = 0)
	// Fall back to legacy repo-level fields (repo_id = repoID)
	fields := make([]*CustomFieldDef, 0, 10)
	return fields, db.GetEngine(ctx).
		Where("((owner_id != 0 AND repo_id = 0) OR repo_id = ?) AND scope = ? AND is_active = ?",
			repoID, CustomFieldScopeIssue, true).
		OrderBy("sort_order ASC, id ASC").
		Find(&fields)
}

// GetAllCustomFieldsByRepo returns all field definitions for a repo (for settings page).
func GetAllCustomFieldsByRepo(ctx context.Context, repoID int64) ([]*CustomFieldDef, error) {
	fields := make([]*CustomFieldDef, 0, 10)
	return fields, db.GetEngine(ctx).
		Where("repo_id = ?", repoID).
		OrderBy("sort_order ASC, id ASC").
		Find(&fields)
}

// ──────────────────────────────────────────────────────────────────────
// Field definition CRUD
// ──────────────────────────────────────────────────────────────────────

// GetCustomFieldDefByID returns a single field definition.
func GetCustomFieldDefByID(ctx context.Context, id int64) (*CustomFieldDef, error) {
	field := new(CustomFieldDef)
	has, err := db.GetEngine(ctx).ID(id).Get(field)
	if err != nil {
		return nil, err
	}
	if !has {
		return nil, db.ErrNotExist{Resource: "CustomFieldDef", ID: id}
	}
	return field, nil
}

// CreateCustomFieldDef creates a new custom field definition.
func CreateCustomFieldDef(ctx context.Context, field *CustomFieldDef) error {
	_, err := db.GetEngine(ctx).Insert(field)
	return err
}

// UpdateCustomFieldDef updates a custom field definition.
func UpdateCustomFieldDef(ctx context.Context, field *CustomFieldDef) error {
	_, err := db.GetEngine(ctx).ID(field.ID).AllCols().Update(field)
	return err
}

// DeleteCustomFieldDef deletes a field definition and all its values.
func DeleteCustomFieldDef(ctx context.Context, id int64) error {
	if _, err := db.GetEngine(ctx).Where("field_id = ?", id).Delete(new(CustomFieldValue)); err != nil {
		return err
	}
	_, err := db.GetEngine(ctx).ID(id).Delete(new(CustomFieldDef))
	return err
}

// ──────────────────────────────────────────────────────────────────────
// Field values — generic entity-based (works for issues and repos)
// ──────────────────────────────────────────────────────────────────────

// GetCustomFieldValuesMap returns field_id -> value for an entity.
func GetCustomFieldValuesMap(ctx context.Context, entityID int64) (map[int64]string, error) {
	values := make([]*CustomFieldValue, 0, 10)
	if err := db.GetEngine(ctx).Where("entity_id = ?", entityID).Find(&values); err != nil {
		return nil, err
	}
	result := make(map[int64]string, len(values))
	for _, v := range values {
		result[v.FieldID] = v.Value
	}
	return result, nil
}

// SetCustomFieldValue creates or updates a single custom field value.
func SetCustomFieldValue(ctx context.Context, entityID, fieldID int64, value string) error {
	existing := new(CustomFieldValue)
	has, err := db.GetEngine(ctx).Where("entity_id = ? AND field_id = ?", entityID, fieldID).Get(existing)
	if err != nil {
		return err
	}
	if has {
		existing.Value = value
		_, err = db.GetEngine(ctx).ID(existing.ID).Cols("value").Update(existing)
		return err
	}
	_, err = db.GetEngine(ctx).Insert(&CustomFieldValue{
		EntityID: entityID,
		FieldID:  fieldID,
		Value:    value,
	})
	return err
}

// SetCustomFieldValues sets multiple custom field values for an entity.
func SetCustomFieldValues(ctx context.Context, entityID int64, values map[int64]string) error {
	for fieldID, value := range values {
		if err := SetCustomFieldValue(ctx, entityID, fieldID, value); err != nil {
			return err
		}
	}
	return nil
}
