package payments

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/go-jet/jet/v2/postgres"
	"github.com/go-jet/jet/v2/qrm"
	"github.com/google/uuid"

	"scrabble/backend/internal/postgres/jet/payments/model"
	"scrabble/backend/internal/postgres/jet/payments/table"
)

// statementOrder is the fixed segment/origin ordering the report renders, so the panel is stable
// (the balance/benefit maps iterate randomly).
var statementOrder = []Source{SourceDirect, SourceVK, SourceTelegram}

// accountStatement reads the account's balances, benefits, refund risk and full ledger history for
// the admin report. Uncached and outside any transaction — an operator view, not a hot path.
func (s *Store) accountStatement(ctx context.Context, accountID uuid.UUID) (Statement, error) {
	st, err := s.loadState(ctx, accountID)
	if err != nil {
		return Statement{}, err
	}
	var out Statement
	for _, src := range statementOrder {
		if chips, ok := st.chips[src]; ok {
			out.Segments = append(out.Segments, SegmentChips{Source: src, Chips: chips})
		}
		if b, ok := st.benefits[src]; ok {
			out.Benefits = append(out.Benefits, OriginBenefit{
				Origin: src, Hints: b.hints, AdsPaidUntil: derefTime(b.adsPaidUntil), AdsForever: b.adsForever,
			})
		}
	}

	var risk model.AccountRisk
	err = postgres.SELECT(table.AccountRisk.AllColumns).
		FROM(table.AccountRisk).
		WHERE(table.AccountRisk.AccountID.EQ(postgres.UUID(accountID))).
		LIMIT(1).
		QueryContext(ctx, s.db, &risk)
	switch {
	case err == nil:
		out.Risk = RiskInfo{Present: true, Abuse: risk.Abuse, LossChips: int(risk.LossChips)}
	case errors.Is(err, qrm.ErrNoRows):
		// no risk row — a clean account
	default:
		return Statement{}, fmt.Errorf("payments: load risk %s: %w", accountID, err)
	}

	var rows []model.Ledger
	if err := postgres.SELECT(table.Ledger.AllColumns).
		FROM(table.Ledger).
		WHERE(table.Ledger.AccountID.EQ(postgres.UUID(accountID))).
		ORDER_BY(table.Ledger.CreatedAt.DESC()).
		QueryContext(ctx, s.db, &rows); err != nil {
		return Statement{}, fmt.Errorf("payments: load ledger %s: %w", accountID, err)
	}
	for _, r := range rows {
		out.Ledger = append(out.Ledger, LedgerEntry{
			Kind:              r.Kind,
			Source:            derefStr(r.Source),
			Origin:            derefStr(r.Origin),
			ChipsDelta:        int(r.ChipsDelta),
			ProductID:         derefUUID(r.ProductID),
			OrderID:           derefUUID(r.OrderID),
			Provider:          derefStr(r.Provider),
			ProviderPaymentID: derefStr(r.ProviderPaymentID),
			Snapshot:          derefStr(r.Snapshot),
			CreatedAt:         r.CreatedAt,
		})
	}

	// Annotate direct-rail entries with the merchant shop (channel) their order was issued through
	// (E10/D44), keyed by the ledger's order id. Non-direct / order-less entries stay "".
	shops, err := s.orderShops(ctx, accountID)
	if err != nil {
		return Statement{}, err
	}
	for i := range out.Ledger {
		out.Ledger[i].Shop = shops[out.Ledger[i].OrderID]
	}
	return out, nil
}

// orderShops maps an account's order ids (as strings) to the merchant shop (channel) each direct
// order was issued through, for annotating the ledger report (E10/D44). Orders with an empty shop
// (non-direct or pre-split) are omitted, so a lookup miss yields "".
func (s *Store) orderShops(ctx context.Context, accountID uuid.UUID) (map[string]string, error) {
	var rows []model.Orders
	if err := postgres.SELECT(table.Orders.OrderID, table.Orders.Shop).
		FROM(table.Orders).
		WHERE(table.Orders.AccountID.EQ(postgres.UUID(accountID))).
		QueryContext(ctx, s.db, &rows); err != nil {
		return nil, fmt.Errorf("payments: load order shops %s: %w", accountID, err)
	}
	m := make(map[string]string, len(rows))
	for _, r := range rows {
		if r.Shop != "" {
			m[r.OrderID.String()] = r.Shop
		}
	}
	return m, nil
}

// allLedger reads the entire append-only ledger (all accounts, newest first) for the admin export.
func (s *Store) allLedger(ctx context.Context) ([]LedgerExportRow, error) {
	var rows []model.Ledger
	if err := postgres.SELECT(table.Ledger.AllColumns).
		FROM(table.Ledger).
		ORDER_BY(table.Ledger.CreatedAt.DESC()).
		QueryContext(ctx, s.db, &rows); err != nil {
		return nil, fmt.Errorf("payments: export ledger: %w", err)
	}
	out := make([]LedgerExportRow, 0, len(rows))
	for _, r := range rows {
		out = append(out, LedgerExportRow{
			AccountID: r.AccountID.String(),
			LedgerEntry: LedgerEntry{
				Kind:              r.Kind,
				Source:            derefStr(r.Source),
				Origin:            derefStr(r.Origin),
				ChipsDelta:        int(r.ChipsDelta),
				ProductID:         derefUUID(r.ProductID),
				OrderID:           derefUUID(r.OrderID),
				Provider:          derefStr(r.Provider),
				ProviderPaymentID: derefStr(r.ProviderPaymentID),
				Snapshot:          derefStr(r.Snapshot),
				CreatedAt:         r.CreatedAt,
			},
		})
	}
	return out, nil
}

// derefStr returns the pointed-to string, or "" when the pointer is nil (a NULL column).
func derefStr(p *string) string {
	if p == nil {
		return ""
	}
	return *p
}

// derefUUID renders the pointed-to UUID as a string, or "" when the pointer is nil.
func derefUUID(p *uuid.UUID) string {
	if p == nil {
		return ""
	}
	return p.String()
}

// derefTime returns the pointed-to time, or the zero time when the pointer is nil (a NULL column).
func derefTime(p *time.Time) time.Time {
	if p == nil {
		return time.Time{}
	}
	return *p
}
