package store

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"

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

func (db *DB) SaveChatHistory(ctx context.Context, h ext.ChatHistory) (ext.ChatHistory, error) {
	messages, err := json.Marshal(h.Messages)
	if err != nil {
		return ext.ChatHistory{}, fmt.Errorf("marshal messages: %w", err)
	}
	meta, err := json.Marshal(h.Metadata)
	if err != nil {
		return ext.ChatHistory{}, fmt.Errorf("marshal metadata: %w", err)
	}

	row := db.pool.QueryRow(ctx, `
		insert into chat_histories
			(session_id, title, channel, agent_id, project_id, messages, summary, metadata)
		values ($1, $2, $3, $4, $5, $6, $7, $8)
		returning id, created_at, updated_at
	`,
		h.SessionID, nullStr(h.Title), nullStr(h.Channel), nullStr(h.AgentID),
		h.ProjectID, messages, nullStr(h.Summary), meta,
	)

	created := h
	if err := row.Scan(&created.ID, &created.CreatedAt, &created.UpdatedAt); err != nil {
		return ext.ChatHistory{}, fmt.Errorf("insert chat history: %w", err)
	}
	return created, nil
}

func (db *DB) GetChatHistory(ctx context.Context, id uuid.UUID) (ext.ChatHistory, bool, error) {
	row := db.pool.QueryRow(ctx, `
		select id, session_id, title, channel, agent_id, project_id,
		       messages, summary, metadata, created_at, updated_at
		from chat_histories where id = $1
	`, id)
	h, err := scanChatHistory(row)
	if err == pgx.ErrNoRows {
		return ext.ChatHistory{}, false, nil
	}
	if err != nil {
		return ext.ChatHistory{}, false, fmt.Errorf("get chat history: %w", err)
	}
	return h, true, nil
}

func (db *DB) GetChatHistoryBySessionID(ctx context.Context, sessionID string) (ext.ChatHistory, bool, error) {
	row := db.pool.QueryRow(ctx, `
		select id, session_id, title, channel, agent_id, project_id,
		       messages, summary, metadata, created_at, updated_at
		from chat_histories
		where session_id = $1
		order by created_at desc
		limit 1
	`, sessionID)
	h, err := scanChatHistory(row)
	if err == pgx.ErrNoRows {
		return ext.ChatHistory{}, false, nil
	}
	if err != nil {
		return ext.ChatHistory{}, false, fmt.Errorf("get chat history by session: %w", err)
	}
	return h, true, nil
}

type ListChatHistoriesFilter struct {
	ProjectID *uuid.UUID
	Channel   string
	AgentID   string
	SessionID string
	Days      int
	Limit     int
}

func (db *DB) ListChatHistories(ctx context.Context, f ListChatHistoriesFilter) ([]ext.ChatHistory, error) {
	limit := f.Limit
	if limit <= 0 {
		limit = 20
	}
	if limit > 200 {
		limit = 200
	}

	conditions := []string{}
	args := []any{}

	if f.ProjectID != nil {
		args = append(args, *f.ProjectID)
		conditions = append(conditions, fmt.Sprintf("project_id = $%d", len(args)))
	}
	if f.Channel != "" {
		args = append(args, f.Channel)
		conditions = append(conditions, fmt.Sprintf("channel = $%d", len(args)))
	}
	if f.AgentID != "" {
		args = append(args, f.AgentID)
		conditions = append(conditions, fmt.Sprintf("agent_id = $%d", len(args)))
	}
	if f.SessionID != "" {
		args = append(args, f.SessionID)
		conditions = append(conditions, fmt.Sprintf("session_id = $%d", len(args)))
	}
	if f.Days > 0 {
		args = append(args, time.Now().UTC().AddDate(0, 0, -f.Days))
		conditions = append(conditions, fmt.Sprintf("created_at >= $%d", len(args)))
	}

	q := `
		select id, session_id, title, channel, agent_id, project_id,
		       messages, summary, metadata, created_at, updated_at
		from chat_histories`
	if len(conditions) > 0 {
		q += " where " + strings.Join(conditions, " and ")
	}
	args = append(args, limit)
	q += fmt.Sprintf(" order by created_at desc limit $%d", len(args))

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

	var result []ext.ChatHistory
	for rows.Next() {
		h, err := scanChatHistory(rows)
		if err != nil {
			return nil, fmt.Errorf("scan chat history: %w", err)
		}
		result = append(result, h)
	}
	return result, rows.Err()
}

func (db *DB) DeleteChatHistory(ctx context.Context, id uuid.UUID) (bool, error) {
	tag, err := db.pool.Exec(ctx, `delete from chat_histories where id = $1`, id)
	if err != nil {
		return false, fmt.Errorf("delete chat history: %w", err)
	}
	return tag.RowsAffected() > 0, nil
}

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

func scanChatHistory(row rowScanner) (ext.ChatHistory, error) {
	var h ext.ChatHistory
	var title, channel, agentID, summary *string
	var messagesJSON, metaJSON []byte

	if err := row.Scan(
		&h.ID, &h.SessionID, &title, &channel, &agentID, &h.ProjectID,
		&messagesJSON, &summary, &metaJSON, &h.CreatedAt, &h.UpdatedAt,
	); err != nil {
		return ext.ChatHistory{}, err
	}

	h.Title = strVal(title)
	h.Channel = strVal(channel)
	h.AgentID = strVal(agentID)
	h.Summary = strVal(summary)

	if err := json.Unmarshal(messagesJSON, &h.Messages); err != nil {
		return ext.ChatHistory{}, fmt.Errorf("unmarshal messages: %w", err)
	}
	if err := json.Unmarshal(metaJSON, &h.Metadata); err != nil {
		return ext.ChatHistory{}, fmt.Errorf("unmarshal metadata: %w", err)
	}
	if h.Messages == nil {
		h.Messages = []ext.ChatMessage{}
	}
	if h.Metadata == nil {
		h.Metadata = map[string]any{}
	}
	return h, nil
}
