package usecase

import (
	"context"
	"time"

	"github.com/zeromicro/go-zero/core/logx"
)

/*
charge 是一次計點呼叫的預留與結算，沿用既有四個 meter，不新增第五個（RG-04）。

用法：`defer charge.Settle(ctx, &err)` 綁在具名 error 回傳上，這樣任何失敗路徑都退點，
不會出現「AI 失敗了但點數扣掉」。
*/
type charge struct {
	svc     *Service
	uid     int64
	meter   string
	mode    string
	label   string
	source  string
	settled bool
}

func (s *Service) bill(ctx context.Context, uid int64, meter, label, source string) (*charge, error) {
	if s == nil || s.Usage == nil {
		return &charge{settled: true}, nil
	}
	mode, err := s.Usage.PrepareCall(ctx, uid, meter)
	if err != nil {
		return nil, err
	}
	return &charge{svc: s, uid: uid, meter: meter, mode: mode, label: label, source: source}, nil
}

func (c *charge) Settle(ctx context.Context, errp *error) {
	if errp != nil && *errp != nil {
		c.Release(ctx)
		return
	}
	c.Commit(ctx)
}

// Commit 寫入用量事件。使用者已經拿到結果，所以這裡失敗只記錄不轉成錯誤。
func (c *charge) Commit(ctx context.Context) {
	if c == nil || c.settled {
		return
	}
	c.settled = true
	if _, err := c.svc.Usage.RecordCall(ctx, c.uid, c.meter, c.mode, c.label, c.source); err != nil {
		logx.Errorf("usage record uid=%d meter=%s source=%s: %v", c.uid, c.meter, c.source, err)
	}
}

func (c *charge) Release(ctx context.Context) {
	if c == nil || c.settled {
		return
	}
	c.settled = true
	// 要補償的失敗常常就是「請求被取消」，退點必須用活得比它久的 context，否則使用者被扣點。
	rctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second)
	defer cancel()
	if err := c.svc.Usage.ReleaseCall(rctx, c.uid, c.meter, c.mode); err != nil {
		logx.Errorf("usage release uid=%d meter=%s source=%s: %v", c.uid, c.meter, c.source, err)
	}
}
