package storage

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

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/denisovdennis/autohero/internal/model"
)

// OfflineDigestRow is persisted counters + loot lines for the post-offline summary.
type OfflineDigestRow struct {
	MonstersKilled int             `json:"monstersKilled"`
	XPGained       int64           `json:"xpGained"`
	GoldGained     int64           `json:"goldGained"`
	LevelsGained   int             `json:"levelsGained"`
	Deaths         int             `json:"deaths"`
	Revives        int             `json:"revives"`
	Loot           []model.LootDrop `json:"loot"`
	UpdatedAt      time.Time       `json:"updatedAt"`
}

// OfflineDigestStore accumulates hero_offline_digest rows.
type OfflineDigestStore struct {
	pool *pgxpool.Pool
}

func NewOfflineDigestStore(pool *pgxpool.Pool) *OfflineDigestStore {
	return &OfflineDigestStore{pool: pool}
}

// ApplyDelta merges a delta into the hero's digest row (upsert).
func (s *OfflineDigestStore) ApplyDelta(ctx context.Context, heroID int64, d OfflineDigestDelta) error {
	if d.MonstersKilled == 0 && d.XPGained == 0 && d.GoldGained == 0 && d.LevelsGained == 0 &&
		d.Deaths == 0 && d.Revives == 0 && len(d.LootAppend) == 0 {
		return nil
	}
	lootFragment := "[]"
	if len(d.LootAppend) > 0 {
		b, err := json.Marshal(d.LootAppend)
		if err != nil {
			return fmt.Errorf("marshal loot fragment: %w", err)
		}
		lootFragment = string(b)
	}
	_, err := s.pool.Exec(ctx, `
		INSERT INTO hero_offline_digest (
			hero_id, monsters_killed, xp_gained, gold_gained, levels_gained, deaths, revives, loot, updated_at
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, now())
		ON CONFLICT (hero_id) DO UPDATE SET
			monsters_killed = hero_offline_digest.monsters_killed + EXCLUDED.monsters_killed,
			xp_gained = hero_offline_digest.xp_gained + EXCLUDED.xp_gained,
			gold_gained = hero_offline_digest.gold_gained + EXCLUDED.gold_gained,
			levels_gained = hero_offline_digest.levels_gained + EXCLUDED.levels_gained,
			deaths = hero_offline_digest.deaths + EXCLUDED.deaths,
			revives = hero_offline_digest.revives + EXCLUDED.revives,
			loot = hero_offline_digest.loot || EXCLUDED.loot,
			updated_at = now()
	`, heroID, d.MonstersKilled, d.XPGained, d.GoldGained, d.LevelsGained, d.Deaths, d.Revives, lootFragment)
	if err != nil {
		return fmt.Errorf("apply offline digest delta: %w", err)
	}
	return nil
}

// OfflineDigestDelta is a single batch of offline stats to merge.
type OfflineDigestDelta struct {
	MonstersKilled int
	XPGained       int64
	GoldGained     int64
	LevelsGained   int
	Deaths         int
	Revives        int
	LootAppend     []model.LootDrop
}

// Get returns the current digest row or zero values if missing.
func (s *OfflineDigestStore) Get(ctx context.Context, heroID int64) (OfflineDigestRow, error) {
	var row OfflineDigestRow
	var lootRaw []byte
	err := s.pool.QueryRow(ctx, `
		SELECT monsters_killed, xp_gained, gold_gained, levels_gained, deaths, revives, loot, updated_at
		FROM hero_offline_digest WHERE hero_id = $1
	`, heroID).Scan(
		&row.MonstersKilled, &row.XPGained, &row.GoldGained, &row.LevelsGained,
		&row.Deaths, &row.Revives, &lootRaw, &row.UpdatedAt,
	)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			row.Loot = []model.LootDrop{}
			return row, nil
		}
		return OfflineDigestRow{}, fmt.Errorf("get offline digest: %w", err)
	}
	if len(lootRaw) > 0 {
		if err := json.Unmarshal(lootRaw, &row.Loot); err != nil {
			row.Loot = nil
		}
	}
	if row.Loot == nil {
		row.Loot = []model.LootDrop{}
	}
	return row, nil
}

// TakeDelete returns the digest and removes the row (for handing summary to client once).
func (s *OfflineDigestStore) TakeDelete(ctx context.Context, heroID int64) (OfflineDigestRow, error) {
	row, err := s.Get(ctx, heroID)
	if err != nil {
		return OfflineDigestRow{}, err
	}
	if _, err := s.pool.Exec(ctx, `DELETE FROM hero_offline_digest WHERE hero_id = $1`, heroID); err != nil {
		return OfflineDigestRow{}, fmt.Errorf("delete offline digest: %w", err)
	}
	return row, nil
}
