package services

import (
	"database/sql"
	"testing"
	"time"

	"quyun/v2/app/commands/testx"
	"quyun/v2/app/http/tenant/dto"
	"quyun/v2/database"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"

	. "github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/suite"

	_ "go.ipao.vip/atom"
	"go.ipao.vip/atom/contracts"
	"go.ipao.vip/gen/types"
	"go.uber.org/dig"
)

type ContentTestSuiteInjectParams struct {
	dig.In

	DB       *sql.DB
	Initials []contracts.Initial `group:"initials"` // nolint:structcheck
}

type ContentTestSuite struct {
	suite.Suite
	ContentTestSuiteInjectParams
}

func Test_Content(t *testing.T) {
	providers := testx.Default().With(Provide)

	testx.Serve(providers, t, func(p ContentTestSuiteInjectParams) {
		suite.Run(t, &ContentTestSuite{ContentTestSuiteInjectParams: p})
	})
}

func (s *ContentTestSuite) Test_Create() {
	Convey("Content.Create", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(1)
		userID := int64(2)

		database.Truncate(ctx, s.DB, models.TableNameContent)

		Convey("成功创建草稿内容并应用默认策略", func() {
			form := &dto.ContentCreateForm{
				Title:       "标题",
				Description: "描述",
			}
			m, err := Content.Create(ctx, tenantID, userID, form)
			So(err, ShouldBeNil)
			So(m, ShouldNotBeNil)
			So(m.TenantID, ShouldEqual, tenantID)
			So(m.UserID, ShouldEqual, userID)
			So(m.Status, ShouldEqual, consts.ContentStatusDraft)
			So(m.Visibility, ShouldEqual, consts.ContentVisibilityTenantOnly)
			So(m.PreviewSeconds, ShouldEqual, consts.DefaultContentPreviewSeconds)
			So(m.PreviewDownloadable, ShouldBeFalse)
		})
	})
}

func (s *ContentTestSuite) Test_Update() {
	Convey("Content.Update", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(1)
		userID := int64(2)

		database.Truncate(ctx, s.DB, models.TableNameContent)

		m := &models.Content{
			TenantID:            tenantID,
			UserID:              userID,
			Title:               "标题",
			Description:         "描述",
			Status:              consts.ContentStatusDraft,
			Visibility:          consts.ContentVisibilityTenantOnly,
			PreviewSeconds:      consts.DefaultContentPreviewSeconds,
			PreviewDownloadable: false,
		}
		So(m.Create(ctx), ShouldBeNil)

		Convey("发布内容应写入 published_at", func() {
			status := consts.ContentStatusPublished
			form := &dto.ContentUpdateForm{Status: &status}

			updated, err := Content.Update(ctx, tenantID, userID, m.ID, form)
			So(err, ShouldBeNil)
			So(updated, ShouldNotBeNil)
			So(updated.Status, ShouldEqual, consts.ContentStatusPublished)
			So(updated.PublishedAt.IsZero(), ShouldBeFalse)
		})
	})
}

func (s *ContentTestSuite) Test_UpsertPrice() {
	Convey("Content.UpsertPrice", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(1)
		userID := int64(2)

		database.Truncate(ctx, s.DB, models.TableNameContentPrice, models.TableNameContent)

		content := &models.Content{
			TenantID:            tenantID,
			UserID:              userID,
			Title:               "标题",
			Description:         "描述",
			Status:              consts.ContentStatusDraft,
			Visibility:          consts.ContentVisibilityTenantOnly,
			PreviewSeconds:      consts.DefaultContentPreviewSeconds,
			PreviewDownloadable: false,
		}
		So(content.Create(ctx), ShouldBeNil)

		Convey("首次 upsert 应创建价格记录", func() {
			form := &dto.ContentPriceUpsertForm{
				PriceAmount: 100,
			}
			price, err := Content.UpsertPrice(ctx, tenantID, userID, content.ID, form)
			So(err, ShouldBeNil)
			So(price, ShouldNotBeNil)
			So(price.PriceAmount, ShouldEqual, 100)
			So(price.Currency, ShouldEqual, consts.CurrencyCNY)
			So(price.DiscountType, ShouldEqual, consts.DiscountTypeNone)
		})

		Convey("再次 upsert 应更新价格记录", func() {
			form1 := &dto.ContentPriceUpsertForm{PriceAmount: 100}
			_, err := Content.UpsertPrice(ctx, tenantID, userID, content.ID, form1)
			So(err, ShouldBeNil)

			form2 := &dto.ContentPriceUpsertForm{
				PriceAmount:   200,
				DiscountType:  consts.DiscountTypePercent,
				DiscountValue: 10,
			}
			price2, err := Content.UpsertPrice(ctx, tenantID, userID, content.ID, form2)
			So(err, ShouldBeNil)
			So(price2.PriceAmount, ShouldEqual, 200)
			So(price2.DiscountType, ShouldEqual, consts.DiscountTypePercent)
			So(price2.DiscountValue, ShouldEqual, 10)
		})
	})
}

func (s *ContentTestSuite) Test_AttachAsset() {
	Convey("Content.AttachAsset", s.T(), func() {
		ctx := s.T().Context()
		now := time.Now().UTC()
		tenantID := int64(1)
		userID := int64(2)

		database.Truncate(ctx, s.DB, models.TableNameContentAsset, models.TableNameMediaAsset, models.TableNameContent)

		content := &models.Content{
			TenantID:            tenantID,
			UserID:              userID,
			Title:               "标题",
			Description:         "描述",
			Status:              consts.ContentStatusDraft,
			Visibility:          consts.ContentVisibilityTenantOnly,
			PreviewSeconds:      consts.DefaultContentPreviewSeconds,
			PreviewDownloadable: false,
		}
		So(content.Create(ctx), ShouldBeNil)

		asset := &models.MediaAsset{
			TenantID:  tenantID,
			UserID:    userID,
			Type:      consts.MediaAssetTypeVideo,
			Status:    consts.MediaAssetStatusReady,
			Provider:  "test",
			Bucket:    "bucket",
			ObjectKey: "obj",
			Meta:      types.JSON([]byte("{}")),
		}
		So(asset.Create(ctx), ShouldBeNil)

		Convey("成功绑定资源到内容", func() {
			m, err := Content.AttachAsset(ctx, tenantID, userID, content.ID, asset.ID, consts.ContentAssetRoleMain, 1, now)
			So(err, ShouldBeNil)
			So(m, ShouldNotBeNil)
			So(m.ContentID, ShouldEqual, content.ID)
			So(m.AssetID, ShouldEqual, asset.ID)
			So(m.Role, ShouldEqual, consts.ContentAssetRoleMain)
		})
	})
}

func (s *ContentTestSuite) Test_HasAccess() {
	Convey("Content.HasAccess", s.T(), func() {
		ctx := s.T().Context()
		now := time.Now().UTC()
		tenantID := int64(1)
		userID := int64(2)

		database.Truncate(ctx, s.DB, models.TableNameContentAccess, models.TableNameContent)

		content := &models.Content{
			TenantID:            tenantID,
			UserID:              userID,
			Title:               "标题",
			Description:         "描述",
			Status:              consts.ContentStatusPublished,
			Visibility:          consts.ContentVisibilityTenantOnly,
			PreviewSeconds:      consts.DefaultContentPreviewSeconds,
			PreviewDownloadable: false,
			PublishedAt:         now,
		}
		So(content.Create(ctx), ShouldBeNil)

		Convey("未授予权益应返回 false", func() {
			ok, err := Content.HasAccess(ctx, tenantID, userID, content.ID)
			So(err, ShouldBeNil)
			So(ok, ShouldBeFalse)
		})

		Convey("权益 active 应返回 true", func() {
			access := &models.ContentAccess{
				TenantID:  tenantID,
				UserID:    userID,
				ContentID: content.ID,
				OrderID:   0,
				Status:    consts.ContentAccessStatusActive,
				RevokedAt: time.Time{},
				CreatedAt: now,
				UpdatedAt: now,
			}
			So(access.Create(ctx), ShouldBeNil)

			ok, err := Content.HasAccess(ctx, tenantID, userID, content.ID)
			So(err, ShouldBeNil)
			So(ok, ShouldBeTrue)
		})
	})
}
