//
//  Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
//  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 dao

import (
	"context"
	"fmt"
	"ragflow/internal/entity"

	"gorm.io/gorm"
)

// CompilationTemplateDAO is the data-access object for compilation templates and
// their groups.
type CompilationTemplateDAO struct{}

// NewCompilationTemplateDAO creates a compilation template DAO.
func NewCompilationTemplateDAO() *CompilationTemplateDAO {
	return &CompilationTemplateDAO{}
}

// ResolveGroupTemplateIDs resolves compilation-template-group ids to their child
// compilation_template ids for the given tenant. It mirrors the Python
// CompilationTemplateGroupService.resolve_template_ids: only VALID templates
// whose owning group is VALID and belongs to the tenant are returned, ordered by
// create_time within each group and by the requested group order across groups
// (so the output is deterministic).
//
// An error is returned if any requested group id does not resolve to at least
// one valid template (group missing, wrong tenant, or empty). This surfaces the
// misconfiguration instead of silently dropping the compilation_template_ids
// stamp — the data-loss path the caller is guarding against.
func (dao *CompilationTemplateDAO) ResolveGroupTemplateIDs(ctx context.Context, tenantID string, groupIDs []string) ([]string, error) {
	if len(groupIDs) == 0 {
		return nil, nil
	}

	db := DB.WithContext(ctx)

	// Verify the requested groups exist and are valid. The built-in group
	// (compiler.json's default) is a global, tenant-agnostic catalogue, so it
	// resolves for every tenant; other groups are scoped to the caller's
	// tenant.
	isBuiltin := make(map[string]bool, len(groupIDs))
	for _, gid := range groupIDs {
		if gid == BuiltinCompilationTemplateGroupID {
			isBuiltin[gid] = true
		}
	}
	var validGroups []entity.CompilationTemplateGroup
	if err := db.
		Where("id IN ? AND status = ?", groupIDs, string(entity.StatusValid)).
		Find(&validGroups).Error; err != nil {
		return nil, fmt.Errorf("resolve compilation template groups: %w", err)
	}
	valid := make(map[string]struct{}, len(validGroups))
	byID := make(map[string]entity.CompilationTemplateGroup, len(validGroups))
	for _, g := range validGroups {
		valid[g.ID] = struct{}{}
		byID[g.ID] = g
	}
	for _, gid := range groupIDs {
		if _, ok := valid[gid]; !ok {
			return nil, fmt.Errorf("compilation_template_group %q not found for tenant %q", gid, tenantID)
		}
		// Non-built-in groups must belong to the requesting tenant.
		if !isBuiltin[gid] {
			if byID[gid].TenantID != tenantID {
				return nil, fmt.Errorf("compilation_template_group %q not found for tenant %q", gid, tenantID)
			}
		}
	}

	var templates []entity.CompilationTemplate
	if err := db.Model(&entity.CompilationTemplate{}).
		Where("group_id IN ? AND status = ?", groupIDs, string(entity.StatusValid)).
		Order("create_time asc").
		Find(&templates).Error; err != nil {
		return nil, fmt.Errorf("resolve compilation templates for groups: %w", err)
	}

	byGroup := make(map[string][]string, len(groupIDs))
	for _, t := range templates {
		if t.GroupID == nil {
			continue
		}
		byGroup[*t.GroupID] = append(byGroup[*t.GroupID], t.ID)
	}

	var out []string
	for _, gid := range groupIDs {
		ids := byGroup[gid]
		if len(ids) == 0 {
			return nil, fmt.Errorf("compilation_template_group %q resolved to no valid templates", gid)
		}
		out = append(out, ids...)
	}
	return out, nil
}

// GetTemplate loads a single, valid compilation template by id for the tenant.
// It backs the KnowledgeCompiler TemplateResolver: the template's kind selects
// the Go compile variant (see common.KindToVariant) and its config is the
// template "content" plumbed to the variant.
func (dao *CompilationTemplateDAO) GetTemplate(ctx context.Context, tenantID, templateID string) (*entity.CompilationTemplate, error) {
	var t entity.CompilationTemplate
	if err := DB.WithContext(ctx).
		Where("id = ? AND tenant_id = ? AND status = ?", templateID, tenantID, string(entity.StatusValid)).
		First(&t).Error; err != nil {
		return nil, fmt.Errorf("load compilation template %q: %w", templateID, err)
	}
	return &t, nil
}

// ListByGroup returns the valid compilation templates belonging to a group,
// ordered by create_time asc, mirroring Python
// CompilationTemplateGroupService child loading.
func (dao *CompilationTemplateDAO) ListByGroup(ctx context.Context, db *gorm.DB, groupID string) ([]*entity.CompilationTemplate, error) {
	var templates []*entity.CompilationTemplate
	if err := db.WithContext(ctx).
		Where("group_id = ? AND status = ?", groupID, string(entity.StatusValid)).
		Order("create_time asc").
		Find(&templates).Error; err != nil {
		return nil, err
	}
	return templates, nil
}

// ListBuiltins returns the valid, built-in (is_builtin) compilation templates,
// ordered by create_time then name. Mirrors Python list_builtins().
func (dao *CompilationTemplateDAO) ListBuiltins(ctx context.Context) ([]*entity.CompilationTemplate, error) {
	var templates []*entity.CompilationTemplate
	if err := DB.WithContext(ctx).
		Where("is_builtin = ? AND status = ?", true, string(entity.StatusValid)).
		Order("create_time asc, name asc").
		Find(&templates).Error; err != nil {
		return nil, err
	}
	return templates, nil
}

// NameExistsInGroup reports whether a valid, non-built-in template with the
// given name already exists inside the group, excluding excludeID. Mirrors the
// Python duplicate-child guard.
func (dao *CompilationTemplateDAO) NameExistsInGroup(ctx context.Context, tenantID, groupID, name, excludeID string) (bool, error) {
	q := DB.WithContext(ctx).Model(&entity.CompilationTemplate{}).
		Where("tenant_id = ? AND group_id = ? AND name = ? AND is_builtin = ? AND status = ?",
			tenantID, groupID, name, false, string(entity.StatusValid))
	if excludeID != "" {
		q = q.Where("id <> ?", excludeID)
	}
	var count int64
	if err := q.Count(&count).Error; err != nil {
		return false, err
	}
	return count > 0, nil
}

// GetByID returns a single template row by id regardless of tenant scope
// (used for reconciling group children).
func (dao *CompilationTemplateDAO) GetByID(ctx context.Context, id string) (*entity.CompilationTemplate, error) {
	var t entity.CompilationTemplate
	if err := DB.WithContext(ctx).Where("id = ?", id).First(&t).Error; err != nil {
		return nil, err
	}
	return &t, nil
}

// Save inserts a new template row.
func (dao *CompilationTemplateDAO) Save(ctx context.Context, db *gorm.DB, t *entity.CompilationTemplate) error {
	return db.WithContext(ctx).Create(t).Error
}

// UpdateFields persists the supplied columns for the template with the given id.
func (dao *CompilationTemplateDAO) UpdateFields(ctx context.Context, db *gorm.DB, id string, m map[string]interface{}) error {
	return db.WithContext(ctx).Model(&entity.CompilationTemplate{}).
		Where("id = ?", id).Updates(m).Error
}

// UpdateStatusByGroup flips the status of every valid template in a group,
// mirroring Python group delete's child cascade.
func (dao *CompilationTemplateDAO) UpdateStatusByGroup(ctx context.Context, db *gorm.DB, groupID, status string) error {
	return db.WithContext(ctx).Model(&entity.CompilationTemplate{}).
		Where("group_id = ? AND status = ?", groupID, string(entity.StatusValid)).
		Update("status", status).Error
}

// UpdateStatusByID flips a single template's status.
func (dao *CompilationTemplateDAO) UpdateStatusByID(ctx context.Context, db *gorm.DB, id, status string) error {
	return db.WithContext(ctx).Model(&entity.CompilationTemplate{}).
		Where("id = ?", id).
		Update("status", status).Error
}

// HardDeleteOrphansByName physically removes stale, invalid, non-built-in
// templates of the given name within the group, mirroring Python
// _purge_stale_invalid_children (which DELETEs orphaned duplicate names after a
// group child is soft-deleted). The deletion is scoped to the group so a
// same-named template in another group is never affected. These rows were
// soft-deleted in a prior operation but must be permanently purged to keep the
// table clean.
func (dao *CompilationTemplateDAO) HardDeleteOrphansByName(ctx context.Context, db *gorm.DB, tenantID, groupID, name string) error {
	return db.WithContext(ctx).Where(
		"tenant_id = ? AND group_id = ? AND name = ? AND is_builtin = ? AND status = ?",
		tenantID, groupID, name, false, string(entity.StatusInvalid),
	).Delete(&entity.CompilationTemplate{}).Error
}
