package http

import (
	_ "embed"
	"strconv"
	"time"

	"quyun/app/jobs"
	"quyun/app/models"
	"quyun/app/requests"
	"quyun/database/conds"
	"quyun/database/fields"
	"quyun/database/schemas/public/model"
	"quyun/providers/ali"
	"quyun/providers/job"
	"quyun/providers/wepay"

	"github.com/go-pay/gopay/wechat/v3"
	"github.com/gofiber/fiber/v3"
	"github.com/pkg/errors"
	"github.com/samber/lo"
	log "github.com/sirupsen/logrus"
)

type ListQuery struct {
	Keyword *string `query:"keyword"`
}

// @provider
type posts struct {
	wepay *wepay.Client
	oss   *ali.OSSClient
	job   *job.Job
}

// List posts
//
//	@Router	/posts [get]
//	@Bind	pagination query
//	@Bind	query query
//	@Bind	user local
func (ctl *posts) List(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery, user *model.Users) (*requests.Pager, error) {
	conds := []conds.Cond{
		conds.Post_NotDeleted(),
		conds.Post_Status(fields.PostStatusPublished),
		conds.Post_Like(query.Keyword),
	}

	pager, err := models.Posts.List(ctx.Context(), pagination, conds...)
	if err != nil {
		log.WithError(err).Errorf("post list err: %v", err)
		return nil, err
	}

	postIds := lo.Map(pager.Items.([]model.Posts), func(item model.Posts, _ int) int64 { return item.ID })
	if len(postIds) > 0 {
		userBoughtIds, err := models.Users.BatchCheckHasBought(ctx.Context(), user.ID, postIds)
		if err != nil {
			log.WithError(err).Errorf("BatchCheckHasBought err: %v", err)
		}

		items := lo.FilterMap(pager.Items.([]model.Posts), func(item model.Posts, _ int) (PostItem, bool) {
			medias, err := models.Posts.GetMediaByIds(ctx.Context(), item.HeadImages.Data)
			if err != nil {
				log.Errorf("GetMediaByIds err: %v", err)
				return PostItem{}, false
			}
			mediaUrls := lo.FilterMap(medias, func(item model.Medias, _ int) (string, bool) {
				url, err := ctl.oss.GetSignedUrl(ctx.Context(), item.Path)
				if err != nil {
					log.WithError(err).Errorf("head image GetSignedUrl err: %v", err)
					return "", false
				}

				return url, true
			})

			_, bought := userBoughtIds[item.ID]

			return PostItem{
				ID:          item.ID,
				Title:       item.Title,
				Description: item.Description,
				Price:       item.Price,
				Discount:    item.Discount,
				Views:       item.Views,
				Likes:       item.Likes,
				Tags:        item.Tags.Data,
				HeadImages:  mediaUrls,
				Bought:      bought,
			}, true
		})

		pager.Items = items
	}

	return pager, nil
}

type PostItem struct {
	ID          int64    `json:"id"`
	Bought      bool     `json:"bought"`
	Title       string   `json:"title"`
	Description string   `json:"description"`
	Content     string   `json:"content"`
	Price       int64    `json:"price"`
	Discount    int16    `json:"discount"`
	Views       int64    `json:"views"`
	Likes       int64    `json:"likes"`
	Tags        []string `json:"tags"`
	HeadImages  []string `json:"head_images"`
}

// Show
//
//	@Router	/posts/:id/show [get]
//	@Bind	id path
//	@Bind	user local
func (ctl *posts) Show(ctx fiber.Ctx, id int64, user *model.Users) (*PostItem, error) {
	log.Infof("Fetching post with ID: %d", id)

	post, err := models.Posts.GetByID(ctx.Context(), id, conds.Post_NotDeleted(), conds.Post_Status(fields.PostStatusPublished))
	if err != nil {
		log.WithError(err).Errorf("GetByID err: %v", err)
		return nil, err
	}

	bought, err := models.Users.HasBought(ctx.Context(), user.ID, post.ID)
	if err != nil {
		return nil, err
	}

	medias, err := models.Posts.GetMediaByIds(ctx.Context(), post.HeadImages.Data)
	if err != nil {
		return nil, err
	}
	mediaUrls := lo.FilterMap(medias, func(item model.Medias, _ int) (string, bool) {
		url, err := ctl.oss.GetSignedUrl(ctx.Context(), item.Path)
		if err != nil {
			return "", false
		}

		return url, true
	})

	return &PostItem{
		ID:          post.ID,
		Title:       post.Title,
		Description: post.Description,
		Content:     post.Content,
		Price:       post.Price,
		Discount:    post.Discount,
		Views:       post.Views,
		Likes:       post.Likes,
		Tags:        post.Tags.Data,
		HeadImages:  mediaUrls,
		Bought:      bought,
	}, nil
}

type PlayUrl struct {
	Url string `json:"url"`
}

// Play
//
//	@Router	/posts/:id/play [get]
//	@Bind	id path
//	@Bind	user local
func (ctl *posts) Play(ctx fiber.Ctx, id int64, user *model.Users) (*PlayUrl, error) {
	log := log.WithField("PlayPostID", strconv.FormatInt(id, 10))
	// return &PlayUrl{
	// 	Url: "https://github.com/mediaelement/mediaelement-files/raw/refs/heads/master/big_buck_bunny.mp4",
	// }, nil

	preview := false
	bought, err := models.Users.HasBought(ctx.Context(), user.ID, id)
	if !bought || err != nil {
		preview = true
	}

	log.Infof("Fetching play URL for post ID: %d", id)
	post, err := models.Posts.GetByID(ctx.Context(), id)
	if err != nil {
		log.WithError(err).Errorf("GetByID err: %v", err)
		return nil, err
	}
	go models.Posts.IncrViewCount(ctx.Context(), post.ID)

	for _, asset := range post.Assets.Data {
		if asset.Type == "video/mp4" && asset.Metas != nil && asset.Metas.Short == preview {
			media, err := models.Medias.GetByID(ctx.Context(), asset.Media)
			if err != nil {
				log.WithError(err).Errorf("medias GetByID err: %v", err)
				return nil, err
			}
			duration := asset.Metas.Duration + 30
			if asset.Metas.Duration == 0 {
				duration = 60 * 5
			}
			url, err := ctl.oss.GetSignedUrl(ctx.Context(), media.Path, ali.WithExpire(time.Second*time.Duration(duration)))
			if err != nil {
				log.WithError(err).Errorf("media GetSignedUrl err: %v", err)
				return nil, err
			}
			return &PlayUrl{Url: url}, nil
		}
	}
	return nil, errors.New("视频不存在")
}

// Mine posts
//
//	@Router	/posts/mine [get]
//	@Bind	pagination query
//	@Bind	query query
//	@Bind	user local
func (ctl *posts) Mine(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery, user *model.Users) (*requests.Pager, error) {
	log.Infof("Fetching posts for user with pagination: %+v and keyword: %v", pagination, query.Keyword)

	conds := []conds.Cond{
		conds.Post_NotDeleted(),
		conds.Post_Status(fields.PostStatusPublished),
		conds.Post_Like(query.Keyword),
	}
	pager, err := models.Users.PostList(ctx.Context(), user.ID, pagination, conds...)
	if err != nil {
		log.WithError(err).Errorf("post list err: %v", err)
		return nil, err
	}

	postIds := lo.Map(pager.Items.([]model.Posts), func(item model.Posts, _ int) int64 { return item.ID })
	if len(postIds) > 0 {
		items := lo.FilterMap(pager.Items.([]model.Posts), func(item model.Posts, _ int) (PostItem, bool) {
			medias, err := models.Posts.GetMediaByIds(ctx.Context(), item.HeadImages.Data)
			if err != nil {
				log.Errorf("GetMediaByIds err: %v", err)
				return PostItem{}, false
			}
			mediaUrls := lo.FilterMap(medias, func(item model.Medias, _ int) (string, bool) {
				url, err := ctl.oss.GetSignedUrl(ctx.Context(), item.Path)
				if err != nil {
					log.WithError(err).Errorf("head image GetSignedUrl err: %v", err)
					return "", false
				}

				return url, true
			})

			return PostItem{
				ID:          item.ID,
				Title:       item.Title,
				Description: item.Description,
				Price:       item.Price,
				Discount:    item.Discount,
				Views:       item.Views,
				Likes:       item.Likes,
				Tags:        item.Tags.Data,
				HeadImages:  mediaUrls,
			}, true
		})

		pager.Items = items
	}
	return pager, nil
}

// Buy
//
//	@Router	/posts/:id/buy [post]
//	@Bind	id path
//	@Bind	user local
func (ctl *posts) Buy(ctx fiber.Ctx, id int64, user *model.Users) (*wechat.JSAPIPayParams, error) {
	bought, err := models.Users.HasBought(ctx.Context(), user.ID, id)
	if err != nil {
		return nil, errors.New("查询购买失败")
	}

	if bought {
		return nil, errors.New("已经购买过了")
	}

	post, err := models.Posts.GetByID(ctx.Context(), id)
	if err != nil {
		return nil, errors.Wrapf(err, " failed to get post: %d", id)
	}
	payPrice := post.Price * int64(post.Discount) / 100

	order, err := models.Orders.Create(ctx.Context(), user.ID, post.ID)
	if err != nil {
		return nil, errors.Wrap(err, "订单创建失败")
	}

	if user.Balance >= payPrice {
		if err := models.Orders.SetMeta(ctx.Context(), order.ID, func(om fields.OrderMeta) fields.OrderMeta {
			om.CostBalance = payPrice
			return om
		}); err != nil {
			return nil, errors.Wrap(err, "订单创建失败")
		}

		if err := ctl.job.Add(&jobs.BalancePayNotify{OrderNo: order.OrderNo}); err != nil {
			log.Errorf("add job error:%v", err)
			return nil, errors.Wrap(err, "Failed to add job")
		}

		return &wechat.JSAPIPayParams{
			AppId: "balance",
		}, nil
	}

	payPrice = payPrice - user.Balance
	if err := models.Orders.SetMeta(ctx.Context(), order.ID, func(om fields.OrderMeta) fields.OrderMeta {
		om.CostBalance = user.Balance
		return om
	}); err != nil {
		return nil, errors.Wrap(err, "订单创建失败")
	}
	prePayResp, err := ctl.wepay.V3TransactionJsapi(ctx.Context(), func(bm *wepay.BodyMap) {
		bm.
			Expire(30 * time.Minute).
			Description(post.Title).
			OutTradeNo(order.OrderNo).
			Payer(user.OpenID).
			CNYAmount(payPrice)
	})
	if err != nil {
		log.Errorf("wepay.V3TransactionJsapi err: %v", err)
		return nil, errors.Wrap(err, "微信支付失败")
	}

	return prePayResp.PaySignOfJSAPI()
}
