package store

import (
	"context"
	"crypto/rand"
	"errors"
	"strings"
	"time"

	"gorm.io/gorm"
)

// 支付（P5，设计见 PAYMENT_DESIGN.md）：预付积分包充值。
// 两层汇率各管各的：钱→积分 = 本文件的积分包定价；积分→token = SettingTokensPerCredit（已有）。
//
// 三个模型都**不标 isTenantScoped**：
//   - PaymentOrder 归「计费租户」，与请求 ctx 的活跃租户可能不同（共享计费分叉），
//     插件自动注入会写错归属——tenant_id 一律显式赋值、查询显式过滤（RecentRuns 同款教训）。
//   - CreditPack / RedeemCode 是平台级配置与凭证，不属于任何租户。

// 订单状态机：pending → paid | failed | expired；paid →（人工）refunded。
const (
	OrderPending  = "pending"
	OrderPaid     = "paid"
	OrderFailed   = "failed"
	OrderExpired  = "expired"
	OrderRefunded = "refunded"
)

// 渠道名。P5.1 只有 redeem；wechat 在 P5.2 挂上。
const (
	ChannelRedeem = "redeem"
	ChannelWechat = "wechat"
)

// CreditPack 积分包（admin 可改价/上下架；订单锁定下单当时的价与积分，改包不影响已付订单）。
type CreditPack struct {
	BaseModel
	Name         string `gorm:"size:64" json:"name"`
	CreditsMicro int64  `gorm:"column:credits_micro" json:"credits_micro"`
	PriceFen     int64  `gorm:"column:price_fen" json:"price_fen"` // 应付人民币（分）
	Active       bool   `json:"active"`
	Sort         int    `json:"sort"`
}

func (CreditPack) TableName() string { return "sundynix_credit_pack" }

// PaymentOrder 充值订单——支付侧事实源（与 credit_ledger 对账的另一条腿）。
// 兑换码入账也写一行（channel=redeem、amount_fen=0、即时 paid），全部充值一个查法。
type PaymentOrder struct {
	BaseModel
	TenantID     string     `gorm:"size:64;index" json:"tenant_id"` // 计费租户（下单时解析并锁定）
	UserID       string     `gorm:"size:64;index" json:"user_id"`   // 操作人（审计）
	PackID       string     `gorm:"size:24" json:"pack_id"`         // redeem 渠道为空
	AmountFen    int64      `gorm:"column:amount_fen" json:"amount_fen"`
	CreditsMicro int64      `gorm:"column:credits_micro" json:"credits_micro"`
	Channel      string     `gorm:"size:16;index" json:"channel"`
	Status       string     `gorm:"size:16;index" json:"status"`
	ChannelTxn   string     `gorm:"size:128" json:"channel_txn"` // 渠道流水号 / 兑换码 id
	PaidAt       *time.Time `json:"paid_at"`
}

func (PaymentOrder) TableName() string { return "sundynix_payment_order" }

// RedeemCode 兑换码（平台级凭证；admin 生成，任意租户核销一次）。
type RedeemCode struct {
	BaseModel
	Code         string     `gorm:"size:32;uniqueIndex" json:"code"`
	CreditsMicro int64      `gorm:"column:credits_micro" json:"credits_micro"`
	Status       string     `gorm:"size:16;index" json:"status"` // unused / used
	UsedTenant   string     `gorm:"size:64" json:"used_tenant"`
	UsedBy       string     `gorm:"size:64" json:"used_by"`
	UsedAt       *time.Time `json:"used_at"`
	Memo         string     `gorm:"size:255" json:"memo"`
}

func (RedeemCode) TableName() string { return "sundynix_redeem_code" }

// codeAlphabet 去掉易混字符（0/O、1/I/L）的 base32 变体。
const codeAlphabet = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"

// newRedeemCode 生成 SDX-XXXX-XXXX-XXXX 形式的兑换码（crypto/rand，31^12 ≈ 7.9e17 空间）。
func newRedeemCode() (string, error) {
	b := make([]byte, 12)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	var sb strings.Builder
	sb.WriteString("SDX")
	for i, c := range b {
		if i%4 == 0 {
			sb.WriteByte('-')
		}
		sb.WriteByte(codeAlphabet[int(c)%len(codeAlphabet)])
	}
	return sb.String(), nil
}

// GenerateRedeemCodes admin 批量生成兑换码（每张 creditsMicro 积分）。返回明文码列表。
func (p *Postgres) GenerateRedeemCodes(ctx context.Context, n int, creditsMicro int64, memo string) ([]string, error) {
	if p.db == nil {
		return nil, errStoreDisabled
	}
	if n <= 0 || n > 200 || creditsMicro <= 0 {
		return nil, errors.New("数量须在 1-200、面额须为正")
	}
	out := make([]string, 0, n)
	err := p.db.WithContext(WithoutTenant(ctx)).Transaction(func(tx *gorm.DB) error {
		for i := 0; i < n; i++ {
			code, err := newRedeemCode()
			if err != nil {
				return err
			}
			if err := tx.Create(&RedeemCode{Code: code, CreditsMicro: creditsMicro, Status: "unused", Memo: memo}).Error; err != nil {
				return err // 撞唯一索引概率约 n/31^12，整批重试比码内重试省事——直接报错让 admin 再点一次
			}
			out = append(out, code)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return out, nil
}

// ListRedeemCodes admin 查看兑换码（倒序）。
func (p *Postgres) ListRedeemCodes(ctx context.Context, limit int) ([]RedeemCode, error) {
	if p.db == nil {
		return nil, nil
	}
	if limit <= 0 || limit > 500 {
		limit = 100
	}
	var out []RedeemCode
	err := p.db.WithContext(WithoutTenant(ctx)).Order("created_at desc").Limit(limit).Find(&out).Error
	return out, err
}

// Redeem 核销兑换码：一个事务里完成「码 CAS 占用 → 建已支付订单 → 入账（分录+物化余额）」。
// 幂等双闸：码状态 CAS 是主闸（unused→used 只成功一次）；credit_ledger 的
// (kind,ref) 部分唯一索引兜底（ref=订单号）。任何一步失败整体回滚，码不会白烧。
func (p *Postgres) Redeem(ctx context.Context, code, tenantID, userID string) (*PaymentOrder, error) {
	if p.db == nil {
		return nil, errStoreDisabled
	}
	code = strings.ToUpper(strings.TrimSpace(code))
	if code == "" || tenantID == "" {
		return nil, errors.New("兑换码必填")
	}
	var order *PaymentOrder
	// 计费租户可能 ≠ 请求 ctx 的活跃租户，且账本表是 tenant-scoped——旁路插件、全部显式赋值。
	err := p.db.WithContext(WithoutTenant(ctx)).Transaction(func(tx *gorm.DB) error {
		var rc RedeemCode
		if err := tx.First(&rc, "code = ?", code).Error; err != nil {
			return errors.New("兑换码不存在")
		}
		now := time.Now()
		res := tx.Model(&RedeemCode{}).
			Where("id = ? AND status = ?", rc.ID, "unused").
			Updates(map[string]any{"status": "used", "used_tenant": tenantID, "used_by": userID, "used_at": now})
		if res.Error != nil {
			return res.Error
		}
		if res.RowsAffected == 0 {
			return errors.New("兑换码已被使用")
		}
		o := &PaymentOrder{
			TenantID: tenantID, UserID: userID,
			AmountFen: 0, CreditsMicro: rc.CreditsMicro,
			Channel: ChannelRedeem, Status: OrderPaid, ChannelTxn: rc.ID, PaidAt: &now,
		}
		if err := tx.Create(o).Error; err != nil {
			return err
		}
		if err := tx.Create(&CreditLedger{
			TenantID: tenantID, Kind: LedgerGrant, CreditsMicro: rc.CreditsMicro, Ref: o.ID, Memo: "兑换码 " + code,
		}).Error; err != nil {
			return err
		}
		if err := tx.Model(&Tenant{}).Where("id = ?", tenantID).
			UpdateColumn("credit_balance_micro", gorm.Expr("credit_balance_micro + ?", rc.CreditsMicro)).Error; err != nil {
			return err
		}
		order = o
		return nil
	})
	if err != nil {
		return nil, err
	}
	return order, nil
}

// CreateOrder 落一张 pending 订单（价与积分由服务端按包锁定后传入）。
func (p *Postgres) CreateOrder(ctx context.Context, o *PaymentOrder) error {
	if p.db == nil {
		return errStoreDisabled
	}
	return p.db.WithContext(WithoutTenant(ctx)).Create(o).Error
}

// GetOrder 按 id 取订单。
func (p *Postgres) GetOrder(ctx context.Context, id string) (*PaymentOrder, error) {
	if p.db == nil {
		return nil, errStoreDisabled
	}
	var o PaymentOrder
	if err := p.db.WithContext(WithoutTenant(ctx)).First(&o, "id = ?", id).Error; err != nil {
		return nil, err
	}
	return &o, nil
}

// MarkOrderPaid 渠道确认已支付后的入账：一个事务里「订单 CAS(pending→paid) → 分录 → 物化余额」。
// 返回 changed=false 表示这单已被处理过（回调重复推送/回调与主动查单赛跑），幂等直接成功。
// 双闸：CAS 是主闸；credit_ledger (kind,ref=订单号) 唯一索引兜底。
func (p *Postgres) MarkOrderPaid(ctx context.Context, orderID, channelTxn string) (bool, error) {
	if p.db == nil {
		return false, errStoreDisabled
	}
	changed := false
	err := p.db.WithContext(WithoutTenant(ctx)).Transaction(func(tx *gorm.DB) error {
		now := time.Now()
		res := tx.Model(&PaymentOrder{}).
			Where("id = ? AND status = ?", orderID, OrderPending).
			Updates(map[string]any{"status": OrderPaid, "channel_txn": channelTxn, "paid_at": now})
		if res.Error != nil {
			return res.Error
		}
		if res.RowsAffected == 0 {
			return nil // 已处理过（或订单不存在/已过期）——幂等，不重复入账
		}
		var o PaymentOrder
		if err := tx.First(&o, "id = ?", orderID).Error; err != nil {
			return err
		}
		if err := tx.Create(&CreditLedger{
			TenantID: o.TenantID, Kind: LedgerGrant, CreditsMicro: o.CreditsMicro, Ref: o.ID, Memo: "充值 " + o.Channel,
		}).Error; err != nil {
			return err
		}
		if err := tx.Model(&Tenant{}).Where("id = ?", o.TenantID).
			UpdateColumn("credit_balance_micro", gorm.Expr("credit_balance_micro + ?", o.CreditsMicro)).Error; err != nil {
			return err
		}
		changed = true
		return nil
	})
	return changed, err
}

// PendingWechatOrders 捞出所有 pending 的微信订单（掉单补偿定时器扫描用）。
// 只取微信单：兑换码单核销即 paid，永不 pending，不需要查单。按创建时间升序，先补老单。
func (p *Postgres) PendingWechatOrders(ctx context.Context, limit int) ([]PaymentOrder, error) {
	if p.db == nil {
		return nil, nil
	}
	if limit <= 0 || limit > 500 {
		limit = 200
	}
	var out []PaymentOrder
	err := p.db.WithContext(WithoutTenant(ctx)).
		Where("status = ? AND channel = ?", OrderPending, ChannelWechat).
		Order("created_at asc").Limit(limit).Find(&out).Error
	return out, err
}

// ExpireOrder 把超时未付的 pending 单置为 expired（CAS，已 paid 的不动）。
func (p *Postgres) ExpireOrder(ctx context.Context, orderID string) error {
	if p.db == nil {
		return errStoreDisabled
	}
	return p.db.WithContext(WithoutTenant(ctx)).Model(&PaymentOrder{}).
		Where("id = ? AND status = ?", orderID, OrderPending).
		Update("status", OrderExpired).Error
}

// OrderSummary 是 admin 订单流一行（带租户名，免前端二次查）。
type OrderSummary struct {
	PaymentOrder
	TenantName string `json:"tenant_name"`
}

// AllOrders 全平台充值订单流（admin 观测；可按状态过滤）。倒序，翻页。
func (p *Postgres) AllOrders(ctx context.Context, status string, limit int) ([]OrderSummary, error) {
	if p.db == nil {
		return nil, nil
	}
	if limit <= 0 || limit > 200 {
		limit = 50
	}
	q := p.db.WithContext(WithoutTenant(ctx)).Table("sundynix_payment_order o").
		Select("o.*, t.name as tenant_name").
		Joins("LEFT JOIN sundynix_tenant t ON t.id = o.tenant_id").
		Where("o.deleted_at IS NULL")
	if status != "" {
		q = q.Where("o.status = ?", status)
	}
	var out []OrderSummary
	err := q.Order("o.created_at desc").Limit(limit).Scan(&out).Error
	return out, err
}

// OrderStats 全平台订单状态计数（观测卡片：pending/paid/expired 分布 + 累计到账额）。
type OrderStats struct {
	Pending      int64 `json:"pending"`
	Paid         int64 `json:"paid"`
	Expired      int64 `json:"expired"`
	PaidFenTotal int64 `json:"paid_fen_total"` // 累计到账金额（分），只算真渠道 amount_fen>0
}

func (p *Postgres) OrderStats(ctx context.Context) OrderStats {
	var s OrderStats
	if p.db == nil {
		return s
	}
	ctx = WithoutTenant(ctx)
	// 每次都起新 query builder：复用同一个会累加 WHERE（status=A AND status=B → 恒 0）。
	countBy := func(status string) int64 {
		var n int64
		p.db.WithContext(ctx).Model(&PaymentOrder{}).Where("status = ?", status).Count(&n)
		return n
	}
	s.Pending = countBy(OrderPending)
	s.Paid = countBy(OrderPaid)
	s.Expired = countBy(OrderExpired)
	p.db.WithContext(ctx).Model(&PaymentOrder{}).Where("status = ?", OrderPaid).
		Select("coalesce(sum(amount_fen),0)").Scan(&s.PaidFenTotal)
	return s
}

// ReconcileRow 对账差异一行：paid 订单在账本里找不到对应 grant 分录（或反之）。
type ReconcileRow struct {
	OrderID      string `json:"order_id"`
	TenantID     string `json:"tenant_id"`
	CreditsMicro int64  `json:"credits_micro"`
	Issue        string `json:"issue"` // order_without_ledger / ledger_without_order
}

// ReconcileOrders 日终对账：paid 订单 ↔ ledger(kind=grant, ref=订单号) 逐单比对，列出对不上的。
// 正常应返回空列表（双闸保证 paid 单必有且仅有一条 grant 分录）。有差异即数据出了问题，需人工查。
func (p *Postgres) ReconcileOrders(ctx context.Context, limit int) ([]ReconcileRow, error) {
	if p.db == nil {
		return nil, nil
	}
	if limit <= 0 || limit > 500 {
		limit = 200
	}
	ctx = WithoutTenant(ctx)
	var out []ReconcileRow
	// paid 订单但账本无对应 grant 分录（钱记了、积分没到——最严重）
	if err := p.db.WithContext(ctx).
		Raw(`SELECT o.id AS order_id, o.tenant_id, o.credits_micro, 'order_without_ledger' AS issue
		     FROM sundynix_payment_order o
		     WHERE o.status='paid' AND o.deleted_at IS NULL
		       AND NOT EXISTS (SELECT 1 FROM sundynix_credit_ledger l
		                       WHERE l.kind='grant' AND l.ref=o.id AND l.deleted_at IS NULL)
		     ORDER BY o.created_at DESC LIMIT ?`, limit).Scan(&out).Error; err != nil {
		return nil, err
	}
	// grant 分录指向的订单不是 paid（积分到了、订单态不对——重复入账/状态错乱）
	var out2 []ReconcileRow
	if err := p.db.WithContext(ctx).
		Raw(`SELECT l.ref AS order_id, l.tenant_id, l.credits_micro, 'ledger_without_paid_order' AS issue
		     FROM sundynix_credit_ledger l
		     JOIN sundynix_payment_order o ON o.id = l.ref
		     WHERE l.kind='grant' AND l.deleted_at IS NULL AND o.status <> 'paid'
		     ORDER BY l.created_at DESC LIMIT ?`, limit).Scan(&out2).Error; err != nil {
		return nil, err
	}
	return append(out, out2...), nil
}

// GetPack 按 id 取在售积分包（下单锁价用；下架的包不可下单）。
func (p *Postgres) GetPack(ctx context.Context, id string) (*CreditPack, error) {
	if p.db == nil {
		return nil, errStoreDisabled
	}
	var pk CreditPack
	if err := p.db.WithContext(WithoutTenant(ctx)).First(&pk, "id = ? AND active = ?", id, true).Error; err != nil {
		return nil, err
	}
	return &pk, nil
}

// ActivePacks 在售积分包（用户侧展示）。
func (p *Postgres) ActivePacks(ctx context.Context) ([]CreditPack, error) {
	if p.db == nil {
		return nil, nil
	}
	var out []CreditPack
	err := p.db.WithContext(ctx).Where("active = ?", true).Order("sort asc, price_fen asc").Find(&out).Error
	return out, err
}

// SavePack admin 新建/更新积分包（id 空=新建）。
func (p *Postgres) SavePack(ctx context.Context, pk *CreditPack) error {
	if p.db == nil {
		return errStoreDisabled
	}
	if strings.TrimSpace(pk.Name) == "" || pk.CreditsMicro <= 0 || pk.PriceFen < 0 {
		return errors.New("name/credits 必填且为正")
	}
	return p.db.WithContext(WithoutTenant(ctx)).Save(pk).Error
}

// ListPacks admin 查看全部积分包（含下架）。
func (p *Postgres) ListPacks(ctx context.Context) ([]CreditPack, error) {
	if p.db == nil {
		return nil, nil
	}
	var out []CreditPack
	err := p.db.WithContext(WithoutTenant(ctx)).Order("sort asc, created_at asc").Find(&out).Error
	return out, err
}

// TenantOrders 某计费租户的充值订单（倒序）。tenant_id 显式过滤（模型未挂租户插件）。
func (p *Postgres) TenantOrders(ctx context.Context, tenantID string, limit int) ([]PaymentOrder, error) {
	if p.db == nil || tenantID == "" {
		return nil, nil
	}
	if limit <= 0 || limit > 100 {
		limit = 20
	}
	var out []PaymentOrder
	err := p.db.WithContext(WithoutTenant(ctx)).Where("tenant_id = ?", tenantID).
		Order("created_at desc").Limit(limit).Find(&out).Error
	return out, err
}
