package reporting

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

const (
	KnowledgeQAFeedbackRatingHelpful = "helpful"
	KnowledgeQAFeedbackRatingProblem = "problem"
)

var allowedKnowledgeQAFeedbackReasons = map[string]struct{}{
	"incorrect":   {},
	"incomplete":  {},
	"irrelevant":  {},
	"citation":    {},
	"stale":       {},
	"sensitive":   {},
	"malfunction": {},
}

type KnowledgeQAFeedback struct {
	ID        string
	TurnID    string
	Rating    string
	Reasons   []string
	Comment   string
	UpdatedAt time.Time
}

type UpsertKnowledgeQAFeedbackParams struct {
	WorkspaceID     string
	OrganizationID  string
	ThreadChannelID string
	SessionID       string
	TurnID          string
	ReporterUserID  string
	Rating          string
	Reasons         []string
	Comment         string
	Question        string
	Answer          string
	ContextJSON     []byte
	Now             time.Time
}

func (store *Store) UpsertKnowledgeQAFeedback(
	ctx context.Context,
	params UpsertKnowledgeQAFeedbackParams,
) (KnowledgeQAFeedback, error) {
	if store == nil || store.db == nil {
		return KnowledgeQAFeedback{}, fmt.Errorf("reporting store is not configured")
	}
	normalized, err := normalizeKnowledgeQAFeedback(params)
	if err != nil {
		return KnowledgeQAFeedback{}, err
	}
	feedbackID, err := identifiers.NewEntityID("kqf")
	if err != nil {
		return KnowledgeQAFeedback{}, fmt.Errorf("generate Knowledge Q&A feedback id: %w", err)
	}
	reasonsJSON, err := model.MarshalJSONObject("Knowledge Q&A feedback reasons", normalized.Reasons)
	if err != nil {
		return KnowledgeQAFeedback{}, fmt.Errorf("encode Knowledge Q&A feedback reasons: %w", err)
	}
	status := "new"
	if normalized.Rating == KnowledgeQAFeedbackRatingHelpful {
		status = "closed"
	}
	if _, err := store.db.Exec(ctx, `
INSERT INTO knowledge_qa_feedback (
  id, workspace_id, organization_id, thread_channel_id, session_id, turn_id,
  reporter_user_id, rating, reasons_json, comment, question, answer,
  context_json, status, note, created_at, updated_at
) VALUES (
  $1, $2, $3, $4, $5, $6, $7, $8, CAST($9 AS JSON), $10, $11, $12,
  CAST($13 AS JSON), $14, NULL, $15, $15
)
ON DUPLICATE KEY UPDATE
  workspace_id=VALUES(workspace_id),
  organization_id=VALUES(organization_id),
  thread_channel_id=VALUES(thread_channel_id),
  session_id=VALUES(session_id),
  rating=VALUES(rating),
  reasons_json=VALUES(reasons_json),
  comment=VALUES(comment),
  question=VALUES(question),
  answer=VALUES(answer),
  context_json=VALUES(context_json),
  status=VALUES(status),
  note=NULL,
  updated_at=VALUES(updated_at)
`, feedbackID, normalized.WorkspaceID, normalized.OrganizationID,
		normalized.ThreadChannelID, normalized.SessionID, normalized.TurnID,
		normalized.ReporterUserID, normalized.Rating, string(reasonsJSON),
		normalized.Comment, normalized.Question, normalized.Answer,
		string(normalized.ContextJSON), status, normalized.Now,
	); err != nil {
		return KnowledgeQAFeedback{}, fmt.Errorf("upsert Knowledge Q&A feedback: %w", err)
	}
	return store.loadKnowledgeQAFeedback(
		ctx, normalized.WorkspaceID, normalized.ReporterUserID, normalized.TurnID,
	)
}

func (store *Store) ListKnowledgeQAFeedbackForThread(
	ctx context.Context,
	workspaceID string,
	reporterUserID string,
	threadChannelID string,
) ([]KnowledgeQAFeedback, error) {
	if store == nil || store.db == nil {
		return nil, fmt.Errorf("reporting store is not configured")
	}
	workspaceID = strings.TrimSpace(workspaceID)
	reporterUserID = strings.TrimSpace(reporterUserID)
	threadChannelID = strings.TrimSpace(threadChannelID)
	if workspaceID == "" || reporterUserID == "" || threadChannelID == "" {
		return nil, badRequest("workspaceId, reporterUserId, and threadChannelId are required")
	}
	rows, err := store.db.Query(ctx, `
SELECT id, turn_id, rating, reasons_json, comment, updated_at
FROM knowledge_qa_feedback
WHERE workspace_id=$1 AND reporter_user_id=$2 AND thread_channel_id=$3
ORDER BY created_at ASC, id ASC
`, workspaceID, reporterUserID, threadChannelID)
	if err != nil {
		return nil, fmt.Errorf("list Knowledge Q&A feedback: %w", err)
	}
	defer rows.Close()
	feedback := make([]KnowledgeQAFeedback, 0)
	for rows.Next() {
		item, err := scanKnowledgeQAFeedback(rows)
		if err != nil {
			return nil, err
		}
		feedback = append(feedback, item)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate Knowledge Q&A feedback: %w", err)
	}
	return feedback, nil
}

func (store *Store) DeleteKnowledgeQAFeedback(
	ctx context.Context,
	workspaceID string,
	reporterUserID string,
	turnID string,
) error {
	if store == nil || store.db == nil {
		return fmt.Errorf("reporting store is not configured")
	}
	workspaceID = strings.TrimSpace(workspaceID)
	reporterUserID = strings.TrimSpace(reporterUserID)
	turnID = strings.TrimSpace(turnID)
	if workspaceID == "" || reporterUserID == "" || turnID == "" {
		return badRequest("workspaceId, reporterUserId, and turnId are required")
	}
	if _, err := store.db.Exec(ctx, `
DELETE FROM knowledge_qa_feedback
WHERE workspace_id=$1 AND reporter_user_id=$2 AND turn_id=$3
`, workspaceID, reporterUserID, turnID); err != nil {
		return fmt.Errorf("delete Knowledge Q&A feedback: %w", err)
	}
	return nil
}

func (store *Store) loadKnowledgeQAFeedback(
	ctx context.Context,
	workspaceID string,
	reporterUserID string,
	turnID string,
) (KnowledgeQAFeedback, error) {
	return scanKnowledgeQAFeedback(store.db.QueryRow(ctx, `
SELECT id, turn_id, rating, reasons_json, comment, updated_at
FROM knowledge_qa_feedback
WHERE workspace_id=$1 AND reporter_user_id=$2 AND turn_id=$3
LIMIT 1
`, workspaceID, reporterUserID, turnID))
}

type knowledgeQAFeedbackScanner interface{ Scan(...any) error }

func scanKnowledgeQAFeedback(scanner knowledgeQAFeedbackScanner) (KnowledgeQAFeedback, error) {
	var feedback KnowledgeQAFeedback
	var reasonsJSON []byte
	if err := scanner.Scan(
		&feedback.ID, &feedback.TurnID, &feedback.Rating, &reasonsJSON,
		&feedback.Comment, &feedback.UpdatedAt,
	); err != nil {
		return KnowledgeQAFeedback{}, fmt.Errorf("scan Knowledge Q&A feedback: %w", err)
	}
	if err := model.UnmarshalJSONObject("Knowledge Q&A feedback reasons", reasonsJSON, &feedback.Reasons); err != nil {
		return KnowledgeQAFeedback{}, fmt.Errorf("decode Knowledge Q&A feedback reasons: %w", err)
	}
	if feedback.Reasons == nil {
		feedback.Reasons = []string{}
	}
	return feedback, nil
}

func normalizeKnowledgeQAFeedback(
	params UpsertKnowledgeQAFeedbackParams,
) (UpsertKnowledgeQAFeedbackParams, error) {
	normalized := UpsertKnowledgeQAFeedbackParams{
		WorkspaceID:     strings.TrimSpace(params.WorkspaceID),
		OrganizationID:  strings.TrimSpace(params.OrganizationID),
		ThreadChannelID: strings.TrimSpace(params.ThreadChannelID),
		SessionID:       strings.TrimSpace(params.SessionID),
		TurnID:          strings.TrimSpace(params.TurnID),
		ReporterUserID:  strings.TrimSpace(params.ReporterUserID),
		Rating:          strings.ToLower(strings.TrimSpace(params.Rating)),
		Comment:         truncateString(strings.TrimSpace(params.Comment), 4000),
		Question:        strings.TrimSpace(params.Question),
		Answer:          strings.TrimSpace(params.Answer),
		ContextJSON:     append([]byte(nil), params.ContextJSON...),
		Now:             params.Now.UTC(),
	}
	if normalized.WorkspaceID == "" || normalized.OrganizationID == "" ||
		normalized.ThreadChannelID == "" || normalized.SessionID == "" ||
		normalized.TurnID == "" || normalized.ReporterUserID == "" {
		return UpsertKnowledgeQAFeedbackParams{}, badRequest("Knowledge Q&A feedback identity is incomplete")
	}
	if normalized.Rating != KnowledgeQAFeedbackRatingHelpful &&
		normalized.Rating != KnowledgeQAFeedbackRatingProblem {
		return UpsertKnowledgeQAFeedbackParams{}, badRequest("rating is invalid")
	}
	seen := make(map[string]struct{}, len(params.Reasons))
	for _, value := range params.Reasons {
		reason := strings.ToLower(strings.TrimSpace(value))
		if reason == "" {
			continue
		}
		if _, ok := allowedKnowledgeQAFeedbackReasons[reason]; !ok {
			return UpsertKnowledgeQAFeedbackParams{}, badRequest("feedback reason is invalid")
		}
		if _, ok := seen[reason]; ok {
			continue
		}
		seen[reason] = struct{}{}
		normalized.Reasons = append(normalized.Reasons, reason)
	}
	if normalized.Reasons == nil {
		normalized.Reasons = []string{}
	}
	if normalized.Question == "" || normalized.Answer == "" {
		return UpsertKnowledgeQAFeedbackParams{}, badRequest("question and answer are required")
	}
	if len(normalized.ContextJSON) == 0 || len(normalized.ContextJSON) > 1024*1024 || !model.IsValidJSON(normalized.ContextJSON) {
		return UpsertKnowledgeQAFeedbackParams{}, badRequest("context is invalid")
	}
	if normalized.Now.IsZero() {
		normalized.Now = time.Now().UTC()
	}
	return normalized, nil
}
