package kb

import (
	"context"
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"fmt"
	"strings"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func KnowledgeBaseSourceRepositoryAccessOverrideID(workspaceID, kbID, grantID, sourceID, repoRole string) string {
	sum := sha1.Sum([]byte(strings.TrimSpace(workspaceID) + "\x00" + strings.TrimSpace(kbID) + "\x00" + strings.TrimSpace(grantID) + "\x00" + strings.TrimSpace(sourceID) + "\x00" + strings.TrimSpace(repoRole)))
	return "kbsrao_" + hex.EncodeToString(sum[:])[:32]
}

func normalizeKnowledgeBaseSourceRepositoryAccessOverride(row KnowledgeBaseSourceRepositoryAccessOverride) KnowledgeBaseSourceRepositoryAccessOverride {
	row.WorkspaceID = strings.TrimSpace(row.WorkspaceID)
	row.KnowledgeBaseID = strings.TrimSpace(row.KnowledgeBaseID)
	row.GrantID = strings.TrimSpace(row.GrantID)
	row.SourceID = strings.TrimSpace(row.SourceID)
	row.RepoRole = strings.TrimSpace(row.RepoRole)
	row.SubjectType = strings.TrimSpace(row.SubjectType)
	row.SubjectID = strings.TrimSpace(row.SubjectID)
	row.RepositoryAccessProfile = NormalizeKnowledgeBaseRepositoryAccessProfile(row.RepositoryAccessProfile)
	row.CreatedBy = strings.TrimSpace(row.CreatedBy)
	row.UpdatedBy = strings.TrimSpace(row.UpdatedBy)
	if strings.TrimSpace(row.ID) == "" {
		row.ID = KnowledgeBaseSourceRepositoryAccessOverrideID(row.WorkspaceID, row.KnowledgeBaseID, row.GrantID, row.SourceID, row.RepoRole)
	}
	return row
}

func (s *Store) UpsertKnowledgeBaseSourceRepositoryAccessOverride(ctx context.Context, row KnowledgeBaseSourceRepositoryAccessOverride) (KnowledgeBaseSourceRepositoryAccessOverride, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseSourceRepositoryAccessOverride{}, err
	}
	row = normalizeKnowledgeBaseSourceRepositoryAccessOverride(row)
	if row.WorkspaceID == "" || row.KnowledgeBaseID == "" || row.GrantID == "" || row.SourceID == "" || row.RepoRole == "" {
		return KnowledgeBaseSourceRepositoryAccessOverride{}, fmt.Errorf("knowledge base source repository access override identity is required")
	}
	if row.SubjectType == "" || row.SubjectID == "" {
		return KnowledgeBaseSourceRepositoryAccessOverride{}, fmt.Errorf("knowledge base source repository access override subject is required")
	}
	err := s.db.WithContext(ctx).Clauses(clause.OnConflict{
		Columns: []clause.Column{
			{Name: "workspace_id"},
			{Name: "knowledge_base_id"},
			{Name: "grant_id"},
			{Name: "source_id"},
			{Name: "repo_role"},
		},
		DoUpdates: clause.AssignmentColumns([]string{"subject_type", "subject_id", "repository_access_profile", "updated_by", "updated_at"}),
	}).Create(&row).Error
	if err != nil {
		return KnowledgeBaseSourceRepositoryAccessOverride{}, fmt.Errorf("upsert knowledge base source repository access override: %w", err)
	}
	var created KnowledgeBaseSourceRepositoryAccessOverride
	err = s.db.WithContext(ctx).
		Where("workspace_id = ? AND knowledge_base_id = ? AND grant_id = ? AND source_id = ? AND repo_role = ?", row.WorkspaceID, row.KnowledgeBaseID, row.GrantID, row.SourceID, row.RepoRole).
		First(&created).Error
	if err != nil {
		return KnowledgeBaseSourceRepositoryAccessOverride{}, fmt.Errorf("get knowledge base source repository access override after upsert: %w", err)
	}
	return created, nil
}

func (s *Store) ListKnowledgeBaseSourceRepositoryAccessOverrides(ctx context.Context, workspaceID, kbID string, grantIDs []string) ([]KnowledgeBaseSourceRepositoryAccessOverride, error) {
	if err := s.ensureDB(); err != nil {
		return nil, err
	}
	ids := make([]string, 0, len(grantIDs))
	seen := make(map[string]struct{}, len(grantIDs))
	for _, id := range grantIDs {
		id = strings.TrimSpace(id)
		if id == "" {
			continue
		}
		if _, ok := seen[id]; ok {
			continue
		}
		seen[id] = struct{}{}
		ids = append(ids, id)
	}
	if len(ids) == 0 {
		return []KnowledgeBaseSourceRepositoryAccessOverride{}, nil
	}
	var rows []KnowledgeBaseSourceRepositoryAccessOverride
	if err := s.db.WithContext(ctx).
		Where("workspace_id = ? AND knowledge_base_id = ? AND grant_id IN ?", strings.TrimSpace(workspaceID), strings.TrimSpace(kbID), ids).
		Order("source_id ASC").Order("repo_role ASC").
		Find(&rows).Error; err != nil {
		return nil, fmt.Errorf("list knowledge base source repository access overrides: %w", err)
	}
	return rows, nil
}

// GetKnowledgeBaseSourceRepositoryAccessOverride resolves one exact live
// product override. Native Content Store authorization uses this row directly
// rather than trusting the eventually reconciled AGS-era projection.
func (s *Store) GetKnowledgeBaseSourceRepositoryAccessOverride(ctx context.Context, workspaceID, kbID, grantID, sourceID, repoRole string) (KnowledgeBaseSourceRepositoryAccessOverride, error) {
	if err := s.ensureDB(); err != nil {
		return KnowledgeBaseSourceRepositoryAccessOverride{}, err
	}
	var row KnowledgeBaseSourceRepositoryAccessOverride
	err := s.db.WithContext(ctx).
		Where("workspace_id = ? AND knowledge_base_id = ? AND grant_id = ? AND source_id = ? AND repo_role = ?",
			strings.TrimSpace(workspaceID), strings.TrimSpace(kbID), strings.TrimSpace(grantID), strings.TrimSpace(sourceID), strings.TrimSpace(repoRole)).
		First(&row).Error
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return KnowledgeBaseSourceRepositoryAccessOverride{}, fmt.Errorf("get knowledge base source repository access override: %w", err)
	}
	return row, err
}

// ListKnowledgeBaseSourceRepositoryAccessOverridesForSources resolves only the
// caller-selected grants and sources. Both sets are product-bounded before this
// method is called, so retrieval never scans unrelated KB overrides. A KB source
// has exactly three supported repository roles (canonical, mirror, insights),
// which also bounds the result cardinality.
func (s *Store) ListKnowledgeBaseSourceRepositoryAccessOverridesForSources(ctx context.Context, workspaceID string, grantIDs, sourceIDs []string) ([]KnowledgeBaseSourceRepositoryAccessOverride, error) {
	if err := s.ensureDB(); err != nil {
		return nil, err
	}
	grantIDs = uniqueTrimmedStrings(grantIDs)
	sourceIDs = uniqueTrimmedStrings(sourceIDs)
	if len(grantIDs) == 0 || len(sourceIDs) == 0 {
		return []KnowledgeBaseSourceRepositoryAccessOverride{}, nil
	}
	var rows []KnowledgeBaseSourceRepositoryAccessOverride
	if err := s.db.WithContext(ctx).
		Where("workspace_id = ? AND grant_id IN ? AND source_id IN ?", strings.TrimSpace(workspaceID), grantIDs, sourceIDs).
		Limit(len(grantIDs) * len(sourceIDs) * 3).
		Order("grant_id ASC").Order("source_id ASC").Order("repo_role ASC").
		Find(&rows).Error; err != nil {
		return nil, fmt.Errorf("list knowledge base source repository access overrides for sources: %w", err)
	}
	return rows, nil
}

func (s *Store) DeleteKnowledgeBaseSourceRepositoryAccessOverride(ctx context.Context, workspaceID, kbID, grantID, sourceID, repoRole string) error {
	if err := s.ensureDB(); err != nil {
		return err
	}
	result := s.db.WithContext(ctx).
		Where("workspace_id = ? AND knowledge_base_id = ? AND grant_id = ? AND source_id = ? AND repo_role = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(kbID), strings.TrimSpace(grantID), strings.TrimSpace(sourceID), strings.TrimSpace(repoRole)).
		Delete(&KnowledgeBaseSourceRepositoryAccessOverride{})
	if result.Error != nil {
		return fmt.Errorf("delete knowledge base source repository access override: %w", result.Error)
	}
	return nil
}

func (s *Store) DeleteKnowledgeBaseSourceRepositoryAccessOverridesForGrant(ctx context.Context, workspaceID, kbID, grantID string) error {
	if err := s.ensureDB(); err != nil {
		return err
	}
	result := s.db.WithContext(ctx).
		Where("workspace_id = ? AND knowledge_base_id = ? AND grant_id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(kbID), strings.TrimSpace(grantID)).
		Delete(&KnowledgeBaseSourceRepositoryAccessOverride{})
	if result.Error != nil {
		return fmt.Errorf("delete knowledge base source repository access overrides for grant: %w", result.Error)
	}
	return nil
}

func (s *Store) DeleteKnowledgeBaseSourceRepositoryAccessOverridesForSource(ctx context.Context, workspaceID, kbID, sourceID string) error {
	if err := s.ensureDB(); err != nil {
		return err
	}
	result := s.db.WithContext(ctx).
		Where("workspace_id = ? AND knowledge_base_id = ? AND source_id = ?", strings.TrimSpace(workspaceID), strings.TrimSpace(kbID), strings.TrimSpace(sourceID)).
		Delete(&KnowledgeBaseSourceRepositoryAccessOverride{})
	if result.Error != nil {
		return fmt.Errorf("delete knowledge base source repository access overrides for source: %w", result.Error)
	}
	return nil
}
