package service

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/Wei-Shaw/sub2api/internal/config"
	"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
	"go.uber.org/zap"
)

// RequestCaptureLogRepository 定义请求捕获日志的持久化接口。
type RequestCaptureLogRepository interface {
	Create(ctx context.Context, params CreateRequestCaptureLogParams) (int64, error)
	UpdateResponseBody(ctx context.Context, id int64, responseBody string) error
}

// CreateRequestCaptureLogParams 创建请求捕获日志的参数。
type CreateRequestCaptureLogParams struct {
	APIKeyID    int64
	UserID      int64
	RequestID   string
	Path        string
	Method      string
	IPAddress   string
	RequestBody string
	NFSFilePath string
}

// RequestCaptureService 异步捕获指定 API Key 的请求体，写入数据库和 NFS。
type RequestCaptureService struct {
	repo    RequestCaptureLogRepository
	nfsPath string
	timeout time.Duration
}

// nfsFileEnvelope 是写入 NFS 文件的 JSON 结构。
type nfsFileEnvelope struct {
	APIKeyID  int64           `json:"api_key_id"`
	UserID    int64           `json:"user_id"`
	RequestID string          `json:"request_id"`
	CreatedAt time.Time       `json:"created_at"`
	Path      string          `json:"path"`
	Method    string          `json:"method"`
	IPAddress string          `json:"ip_address"`
	Body      json.RawMessage `json:"body"`
}

// NewRequestCaptureService 创建 RequestCaptureService。
func NewRequestCaptureService(repo RequestCaptureLogRepository, cfg *config.Config) *RequestCaptureService {
	timeout := 5 * time.Second
	if cfg != nil && cfg.RequestCapture.WorkerTimeoutSeconds > 0 {
		timeout = time.Duration(cfg.RequestCapture.WorkerTimeoutSeconds) * time.Second
	}
	nfsPath := ""
	if cfg != nil {
		nfsPath = cfg.RequestCapture.NFSPath
	}
	return &RequestCaptureService{
		repo:    repo,
		nfsPath: nfsPath,
		timeout: timeout,
	}
}

// Capture 异步捕获请求体，立即返回 captureID（DB 行 ID），不阻塞调用方。
// 返回 0 表示捕获未启用或写入失败。
// DB 写入与 NFS 写入各自独立，互不影响。
func (s *RequestCaptureService) Capture(
	apiKeyID, userID int64,
	requestID, path, method, ipAddr string,
	body []byte,
) int64 {
	now := time.Now()

	// NFS 写入（独立 goroutine）
	nfsFilePath := ""
	if s.nfsPath != "" {
		nfsFilePath = s.buildNFSFilePath(apiKeyID, requestID, now)
		bodyCopy := make([]byte, len(body))
		copy(bodyCopy, body)
		go s.writeToNFS(nfsFilePath, apiKeyID, userID, requestID, path, method, ipAddr, bodyCopy, now)
	}

	// DB 写入（同步，需要拿到 ID）
	ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
	defer cancel()

	id, err := s.repo.Create(ctx, CreateRequestCaptureLogParams{
		APIKeyID:    apiKeyID,
		UserID:      userID,
		RequestID:   requestID,
		Path:        path,
		Method:      method,
		IPAddress:   ipAddr,
		RequestBody: string(body),
		NFSFilePath: nfsFilePath,
	})
	if err != nil {
		logger.L().Error("request_capture: db write failed",
			zap.Int64("api_key_id", apiKeyID),
			zap.String("request_id", requestID),
			zap.Error(err),
		)
		return 0
	}
	return id
}

// CaptureResponse 异步将响应体写入已有的捕获记录，不阻塞调用方。
// captureID 为 Capture 返回的 ID，为 0 时直接忽略。
func (s *RequestCaptureService) CaptureResponse(captureID int64, responseBody string) {
	if captureID == 0 || responseBody == "" {
		return
	}
	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
		defer cancel()
		if err := s.repo.UpdateResponseBody(ctx, captureID, responseBody); err != nil {
			logger.L().Error("request_capture: db update response failed",
				zap.Int64("capture_id", captureID),
				zap.Error(err),
			)
		}
	}()
}

func (s *RequestCaptureService) buildNFSFilePath(apiKeyID int64, requestID string, t time.Time) string {
	date := t.UTC().Format("2006-01-02")
	filename := fmt.Sprintf("%d_%s.json", t.UnixNano(), requestID)
	return filepath.Join(s.nfsPath, date, fmt.Sprintf("%d", apiKeyID), filename)
}

func (s *RequestCaptureService) writeToNFS(
	filePath string,
	apiKeyID, userID int64,
	requestID, path, method, ipAddr string,
	body []byte,
	now time.Time,
) {
	dir := filepath.Dir(filePath)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		logger.L().Error("request_capture: mkdir failed",
			zap.String("dir", dir),
			zap.Error(err),
		)
		return
	}

	envelope := nfsFileEnvelope{
		APIKeyID:  apiKeyID,
		UserID:    userID,
		RequestID: requestID,
		CreatedAt: now.UTC(),
		Path:      path,
		Method:    method,
		IPAddress: ipAddr,
		Body:      json.RawMessage(body),
	}
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	enc.SetEscapeHTML(false)
	if err := enc.Encode(envelope); err != nil {
		logger.L().Error("request_capture: json marshal failed",
			zap.String("request_id", requestID),
			zap.Error(err),
		)
		return
	}

	if err := os.WriteFile(filePath, buf.Bytes(), 0o644); err != nil {
		logger.L().Error("request_capture: nfs write failed",
			zap.String("file", filePath),
			zap.Error(err),
		)
	}
}
