package user

import (
	"context"
	"database/sql/driver"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/gin-gonic/gin"
	logmodel "github.com/perfect-panel/server/internal/model/log"
	usermodel "github.com/perfect-panel/server/internal/model/user"
	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/pkg/constant"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func TestCommissionReturnLogHandler_HTTPResponse(t *testing.T) {
	gin.SetMode(gin.TestMode)

	db, mock, cleanup := newCommissionReturnHandlerTestDB(t)
	defer cleanup()

	expectCommissionReturnHTTPQueries(mock, 42, 3, logmodel.CommissionTypeRefund, logmodel.CommissionTypeWithdrawReject, logmodel.CommissionTypeWithdrawCancel)
	mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ? OR `content` LIKE ? OR `content` LIKE ?) ORDER BY id DESC LIMIT ?").
		WithArgs(logmodel.TypeCommission.Uint8(), int64(42), "%\"type\":333%", "%\"type\":337%", "%\"type\":338%", 10).
		WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}).
			AddRow(int64(2003), logmodel.TypeCommission.Uint8(), "2023-11-14", int64(42), `{"type":338,"amount":1500,"order_no":"ORDER-3","timestamp":1700000003123}`, time.Unix(1700000000, 0)).
			AddRow(int64(2002), logmodel.TypeCommission.Uint8(), "2023-11-14", int64(42), `{"type":337,"amount":2000,"order_no":"ORDER-2","timestamp":1700000002123}`, time.Unix(1700000000, 0)).
			AddRow(int64(2001), logmodel.TypeCommission.Uint8(), "2023-11-14", int64(42), `{"type":333,"amount":2500,"order_no":"ORDER-1","timestamp":1700000001123}`, time.Unix(1700000000, 0)))

	router := gin.New()
	svcCtx := &svc.ServiceContext{DB: db}
	router.Use(injectTestUser(42))
	router.GET("/v1/public/user/commission_return_log", QueryCommissionReturnLogHandler(svcCtx))

	req := httptest.NewRequest(http.MethodGet, "/v1/public/user/commission_return_log?page=1&size=10", nil)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
	}

	body := strings.TrimSpace(rec.Body.String())
	t.Logf("commission_return_log response: %s", body)
	if !strings.Contains(body, `"event_type":338`) || !strings.Contains(body, `"event_type":337`) || !strings.Contains(body, `"event_type":333`) {
		t.Fatalf("response body = %s", body)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet sql expectations: %v", err)
	}
}

func TestWithdrawalLogHandler_CommissionRefundHTTPResponse(t *testing.T) {
	gin.SetMode(gin.TestMode)

	db, mock, cleanup := newCommissionReturnHandlerTestDB(t)
	defer cleanup()

	expectCommissionReturnHTTPQueries(mock, 42, 1, logmodel.CommissionTypeRefund)
	mock.ExpectQuery("SELECT * FROM `system_logs` WHERE `type` = ? AND object_id = ? AND (`content` LIKE ?) ORDER BY id DESC LIMIT ?").
		WithArgs(logmodel.TypeCommission.Uint8(), int64(42), "%\"type\":333%", 10).
		WillReturnRows(sqlmock.NewRows([]string{"id", "type", "date", "object_id", "content", "created_at"}).
			AddRow(int64(2001), logmodel.TypeCommission.Uint8(), "2023-11-14", int64(42), `{"type":333,"amount":2500,"order_no":"ORDER-1","timestamp":1700000001123}`, time.Unix(1700000000, 0)))

	router := gin.New()
	svcCtx := &svc.ServiceContext{DB: db}
	router.Use(injectTestUser(42))
	router.GET("/v1/public/user/withdrawal_log", QueryWithdrawalLogHandler(svcCtx))

	req := httptest.NewRequest(http.MethodGet, "/v1/public/user/withdrawal_log?page=1&size=10&biz_type=commission_refund", nil)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
	}

	body := strings.TrimSpace(rec.Body.String())
	t.Logf("withdrawal_log commission_refund response: %s", body)
	if !strings.Contains(body, `"biz_type":"commission_refund"`) || !strings.Contains(body, `"amount":2500`) || strings.Contains(body, `"amount":1500`) || strings.Contains(body, `"amount":2000`) {
		t.Fatalf("response body = %s", body)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet sql expectations: %v", err)
	}
}

func newCommissionReturnHandlerTestDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock, func()) {
	t.Helper()

	sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherFunc(func(expectedSQL, actualSQL string) error {
		if strings.Contains(actualSQL, expectedSQL) {
			return nil
		}
		return fmt.Errorf("actual sql %q does not contain %q", actualSQL, expectedSQL)
	})))
	if err != nil {
		t.Fatalf("create sqlmock: %v", err)
	}

	db, err := gorm.Open(mysql.New(mysql.Config{Conn: sqlDB, SkipInitializeWithVersion: true}), &gorm.Config{})
	if err != nil {
		_ = sqlDB.Close()
		t.Fatalf("open gorm db: %v", err)
	}

	return db, mock, func() {
		_ = sqlDB.Close()
	}
}

func injectTestUser(userID int64) gin.HandlerFunc {
	return func(c *gin.Context) {
		ctx := context.WithValue(c.Request.Context(), constant.CtxKeyUser, &usermodel.User{Id: userID})
		c.Request = c.Request.WithContext(ctx)
		c.Next()
	}
}

func expectCommissionReturnHTTPQueries(mock sqlmock.Sqlmock, userID int64, total int64, eventTypes ...uint16) {
	query := "SELECT count(*) FROM `system_logs` WHERE `type` = ? AND object_id = ?"
	args := []driver.Value{logmodel.TypeCommission.Uint8(), userID}
	if len(eventTypes) > 0 {
		clauses := make([]string, 0, len(eventTypes))
		for _, eventType := range eventTypes {
			clauses = append(clauses, "`content` LIKE ?")
			args = append(args, fmt.Sprintf("%%\"type\":%d%%", eventType))
		}
		query += " AND (" + strings.Join(clauses, " OR ") + ")"
	}
	mock.ExpectQuery(query).
		WithArgs(args...).
		WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(total))
}
