package store

import (
	"context"
	"fmt"
	"strings"

	"git.warky.dev/wdevs/amcs/internal/generatedmodels"
	"github.com/jackc/pgx/v5"

	thoughttypes "git.warky.dev/wdevs/amcs/internal/types"
)

func (db *DB) CreateLearning(ctx context.Context, learning thoughttypes.Learning) (thoughttypes.Learning, error) {
	row := db.pool.QueryRow(ctx, `
		insert into learnings (
			summary, details, category, area, status, priority, confidence,
			action_required, source_type, source_ref, project_id, related_thought_id,
			related_skill_id, reviewed_by, reviewed_at, duplicate_of_learning_id,
			supersedes_learning_id, tags
		) values (
			$1, $2, $3, $4, $5, $6, $7,
			$8, $9, $10, $11, $12,
			$13, $14, $15, $16,
			$17, $18
		)
		returning id, guid, created_at, updated_at
	`,
		strings.TrimSpace(learning.Summary),
		strings.TrimSpace(learning.Details),
		strings.TrimSpace(learning.Category),
		strings.TrimSpace(learning.Area),
		string(learning.Status),
		string(learning.Priority),
		string(learning.Confidence),
		learning.ActionRequired,
		nullableText(learning.SourceType),
		nullableText(learning.SourceRef),
		learning.ProjectID,
		learning.RelatedThoughtID,
		learning.RelatedSkillID,
		nullableTextPtr(learning.ReviewedBy),
		learning.ReviewedAt,
		learning.DuplicateOfLearningID,
		learning.SupersedesLearningID,
		learning.Tags,
	)

	created := learning
	var model generatedmodels.ModelPublicLearnings
	if err := row.Scan(&model.ID, &model.GUID, &model.CreatedAt, &model.UpdatedAt); err != nil {
		return thoughttypes.Learning{}, fmt.Errorf("create learning: %w", err)
	}
	created.ID = model.ID.Int64()
	created.GUID = model.GUID.UUID()
	created.CreatedAt = model.CreatedAt.Time()
	created.UpdatedAt = model.UpdatedAt.Time()
	return created, nil
}

func (db *DB) GetLearning(ctx context.Context, id int64) (thoughttypes.Learning, error) {
	row := db.pool.QueryRow(ctx, `
		select id, summary, details, category, area, status, priority, confidence,
		       action_required, source_type, source_ref, project_id, related_thought_id,
		       related_skill_id, reviewed_by, reviewed_at, duplicate_of_learning_id,
		       supersedes_learning_id, tags::text[], created_at, updated_at
		from learnings
		where id = $1
	`, id)

	learning, err := scanLearning(row)
	if err != nil {
		if err == pgx.ErrNoRows {
			return thoughttypes.Learning{}, fmt.Errorf("learning not found: %d", id)
		}
		return thoughttypes.Learning{}, fmt.Errorf("get learning: %w", err)
	}
	return learning, nil
}

func (db *DB) ListLearnings(ctx context.Context, filter thoughttypes.LearningFilter) ([]thoughttypes.Learning, error) {
	args := make([]any, 0, 8)
	conditions := make([]string, 0, 8)

	if filter.ProjectID != nil {
		args = append(args, *filter.ProjectID)
		conditions = append(conditions, fmt.Sprintf("project_id = $%d", len(args)))
	}
	if value := strings.TrimSpace(filter.Category); value != "" {
		args = append(args, value)
		conditions = append(conditions, fmt.Sprintf("category = $%d", len(args)))
	}
	if value := strings.TrimSpace(filter.Area); value != "" {
		args = append(args, value)
		conditions = append(conditions, fmt.Sprintf("area = $%d", len(args)))
	}
	if value := strings.TrimSpace(filter.Status); value != "" {
		args = append(args, value)
		conditions = append(conditions, fmt.Sprintf("status = $%d", len(args)))
	}
	if value := strings.TrimSpace(filter.Priority); value != "" {
		args = append(args, value)
		conditions = append(conditions, fmt.Sprintf("priority = $%d", len(args)))
	}
	if value := strings.TrimSpace(filter.Tag); value != "" {
		args = append(args, value)
		conditions = append(conditions, fmt.Sprintf("$%d = any(tags)", len(args)))
	}
	if value := strings.TrimSpace(filter.Query); value != "" {
		args = append(args, value)
		conditions = append(conditions, fmt.Sprintf("to_tsvector('simple', summary || ' ' || coalesce(details, '')) @@ websearch_to_tsquery('simple', $%d)", len(args)))
	}

	query := `
		select id, summary, details, category, area, status, priority, confidence,
		       action_required, source_type, source_ref, project_id, related_thought_id,
		       related_skill_id, reviewed_by, reviewed_at, duplicate_of_learning_id,
		       supersedes_learning_id, tags::text[], created_at, updated_at
		from learnings
	`
	if len(conditions) > 0 {
		query += " where " + strings.Join(conditions, " and ")
	}
	query += " order by updated_at desc"
	if filter.Limit > 0 {
		args = append(args, filter.Limit)
		query += fmt.Sprintf(" limit $%d", len(args))
	}

	rows, err := db.pool.Query(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("list learnings: %w", err)
	}
	defer rows.Close()

	items := make([]thoughttypes.Learning, 0)
	for rows.Next() {
		item, err := scanLearning(rows)
		if err != nil {
			return nil, fmt.Errorf("scan learning: %w", err)
		}
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate learnings: %w", err)
	}
	return items, nil
}

type learningScanner interface {
	Scan(dest ...any) error
}

func scanLearning(row learningScanner) (thoughttypes.Learning, error) {
	var model generatedmodels.ModelPublicLearnings
	var tags []string

	err := row.Scan(
		&model.ID,
		&model.Summary,
		&model.Details,
		&model.Category,
		&model.Area,
		&model.Status,
		&model.Priority,
		&model.Confidence,
		&model.ActionRequired,
		&model.SourceType,
		&model.SourceRef,
		&model.ProjectID,
		&model.RelatedThoughtID,
		&model.RelatedSkillID,
		&model.ReviewedBy,
		&model.ReviewedAt,
		&model.DuplicateOfLearningID,
		&model.SupersedesLearningID,
		&tags,
		&model.CreatedAt,
		&model.UpdatedAt,
	)
	if err != nil {
		return thoughttypes.Learning{}, err
	}

	if tags == nil {
		tags = []string{}
	}
	return learningFromModel(model, tags), nil
}

func nullableText(value string) *string {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return nil
	}
	return &trimmed
}

func nullableTextPtr(value *string) *string {
	if value == nil {
		return nil
	}
	trimmed := strings.TrimSpace(*value)
	if trimmed == "" {
		return nil
	}
	return &trimmed
}
