package store

import (
	"context"
	"fmt"
	"time"
)

// DialogueSessionRow — one saved follow-up session. Data is the session
// encoded by the dialogue package; the store does not look inside it.
type DialogueSessionRow struct {
	ID      string
	Data    []byte
	Ts      time.Time
	TTL     time.Duration
	Expires time.Time
}

// SaveDialogueSession — write (or replace) the session for one dialogue id.
// One row per id: a newer turn overwrites the older state.
func (s *Store) SaveDialogueSession(ctx context.Context, id string, data []byte, ts time.Time, ttl time.Duration) error {
	expires := ts.Add(ttl)
	_, err := s.db.ExecContext(ctx, `
		INSERT INTO dialogue_sessions (id, data, ts, ttl_ms, expires_ts) VALUES (?, ?, ?, ?, ?)
		ON CONFLICT(id) DO UPDATE SET data       = excluded.data,
		                              ts         = excluded.ts,
		                              ttl_ms     = excluded.ttl_ms,
		                              expires_ts = excluded.expires_ts`,
		id, data, ts.UnixMilli(), ttl.Milliseconds(), expires.UnixMilli())
	if err != nil {
		return fmt.Errorf("save dialogue session: %w", err)
	}
	return nil
}

// DeleteDialogueSession — drop one session (ended, or expired).
func (s *Store) DeleteDialogueSession(ctx context.Context, id string) error {
	if _, err := s.db.ExecContext(ctx, `DELETE FROM dialogue_sessions WHERE id = ?`, id); err != nil {
		return fmt.Errorf("delete dialogue session: %w", err)
	}
	return nil
}

// LoadDialogueSessions — return the sessions still alive at `now` and delete
// the ones that already ran out. An expired session is dead: it never comes
// back after a restart.
func (s *Store) LoadDialogueSessions(ctx context.Context, now time.Time) ([]DialogueSessionRow, error) {
	if _, err := s.db.ExecContext(ctx,
		`DELETE FROM dialogue_sessions WHERE expires_ts <= ?`, now.UnixMilli()); err != nil {
		return nil, fmt.Errorf("prune dialogue sessions: %w", err)
	}
	rows, err := s.db.QueryContext(ctx,
		`SELECT id, data, ts, ttl_ms, expires_ts FROM dialogue_sessions ORDER BY id`)
	if err != nil {
		return nil, fmt.Errorf("load dialogue sessions: %w", err)
	}
	defer rows.Close()
	var out []DialogueSessionRow
	for rows.Next() {
		var r DialogueSessionRow
		var tsMilli, ttlMilli, expMilli int64
		if err := rows.Scan(&r.ID, &r.Data, &tsMilli, &ttlMilli, &expMilli); err != nil {
			return nil, fmt.Errorf("scan dialogue session: %w", err)
		}
		r.Ts = time.UnixMilli(tsMilli).UTC()
		r.TTL = time.Duration(ttlMilli) * time.Millisecond
		r.Expires = time.UnixMilli(expMilli).UTC()
		out = append(out, r)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("load dialogue sessions: %w", err)
	}
	return out, nil
}
