package services

import (
	"context"
	"errors"
	"time"

	"quyun/v2/app/errorx"
	creator_dto "quyun/v2/app/http/v1/dto"
	"quyun/v2/database/fields"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"

	"github.com/google/uuid"
	"github.com/spf13/cast"
	"go.ipao.vip/gen/types"
	"gorm.io/gorm"
)

// @provider
type creator struct{}

func (s *creator) Apply(ctx context.Context, userID int64, form *creator_dto.ApplyForm) error {
	if userID == 0 {
		return errorx.ErrUnauthorized
	}
	uid := userID

	tbl, q := models.TenantQuery.QueryContext(ctx)
	// Check if already has a tenant
	count, _ := q.Where(tbl.UserID.Eq(uid)).Count()
	if count > 0 {
		return errorx.ErrBadRequest.WithMsg("您已是创作者")
	}

	// Create Tenant
	tenant := &models.Tenant{
		UserID: uid,
		Name:   form.Name,
		// Bio/Avatar in config
		Code:   uuid.NewString()[:8], // Generate random code
		UUID:   types.UUID(uuid.New()),
		Status: consts.TenantStatusPendingVerify,
	}

	if err := q.Create(tenant); err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}

	// Also add user as tenant_admin in tenant_users
	tu := &models.TenantUser{
		TenantID: tenant.ID,
		UserID:   uid,
		Role:     types.Array[consts.TenantUserRole]{consts.TenantUserRoleTenantAdmin},
		Status:   consts.UserStatusVerified,
	}
	if err := models.TenantUserQuery.WithContext(ctx).Create(tu); err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}

	return nil
}

func (s *creator) Dashboard(ctx context.Context, userID int64) (*creator_dto.DashboardStats, error) {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return nil, err
	}

	// Followers: count tenant_users
	followers, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(tid)).Count()

	// Revenue: sum tenant_ledgers (income)
	var revenue float64
	// GORM doesn't have a direct Sum method in Gen yet easily accessible without raw SQL or result mapping
	// But we can use underlying DB
	models.TenantLedgerQuery.WithContext(ctx).UnderlyingDB().
		Model(&models.TenantLedger{}).
		Where("tenant_id = ? AND type = ?", tid, consts.TenantLedgerTypeDebitPurchase).
		Select("COALESCE(SUM(amount), 0)").
		Scan(&revenue)

	// Pending Refunds: count orders in refunding
	pendingRefunds, _ := models.OrderQuery.WithContext(ctx).
		Where(models.OrderQuery.TenantID.Eq(tid), models.OrderQuery.Status.Eq(consts.OrderStatusRefunding)).
		Count()

	stats := &creator_dto.DashboardStats{
		TotalFollowers: creator_dto.IntStatItem{Value: int(followers)},
		TotalRevenue:   creator_dto.FloatStatItem{Value: revenue / 100.0},
		PendingRefunds: int(pendingRefunds),
		NewMessages:    0,
	}
	return stats, nil
}

func (s *creator) ListContents(
	ctx context.Context,
	userID int64,
	filter *creator_dto.CreatorContentListFilter,
) ([]creator_dto.CreatorContentItem, error) {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return nil, err
	}

	tbl, q := models.ContentQuery.QueryContext(ctx)
	q = q.Where(tbl.TenantID.Eq(tid))

	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(consts.ContentStatus(*filter.Status)))
	}
	if filter.Genre != nil && *filter.Genre != "" {
		q = q.Where(tbl.Genre.Eq(*filter.Genre))
	}
	if filter.Keyword != nil && *filter.Keyword != "" {
		q = q.Where(tbl.Title.Like("%" + *filter.Keyword + "%"))
	}

	list, err := q.Order(tbl.CreatedAt.Desc()).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	// Fetch Prices
	ids := make([]int64, len(list))
	for i, item := range list {
		ids[i] = item.ID
	}
	priceMap := make(map[int64]float64)
	if len(ids) > 0 {
		prices, _ := models.ContentPriceQuery.WithContext(ctx).Where(models.ContentPriceQuery.ContentID.In(ids...)).Find()
		for _, p := range prices {
			priceMap[p.ContentID] = float64(p.PriceAmount) / 100.0
		}
	}

	var data []creator_dto.CreatorContentItem
	for _, item := range list {
		data = append(data, creator_dto.CreatorContentItem{
			ID:          cast.ToString(item.ID),
			Title:       item.Title,
			Genre:       item.Genre,
			Key:         item.Key,
			Price:       priceMap[item.ID],
			Views:       int(item.Views),
			Likes:       int(item.Likes),
			IsPurchased: false,
		})
	}
	return data, nil
}

func (s *creator) CreateContent(ctx context.Context, userID int64, form *creator_dto.ContentCreateForm) error {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return err
	}
	uid := userID

	return models.Q.Transaction(func(tx *models.Query) error {
		// 1. Create Content
		content := &models.Content{
			TenantID: tid,
			UserID:   uid,
			Title:    form.Title,
			Genre:    form.Genre,
			Key:      form.Key,
			Status:   consts.ContentStatusPublished,
		}
		if err := tx.Content.WithContext(ctx).Create(content); err != nil {
			return err
		}

		// 2. Link Assets
		var assets []*models.ContentAsset
		// Covers
		for i, mid := range form.CoverIDs {
			assets = append(assets, &models.ContentAsset{
				TenantID:  tid,
				UserID:    uid,
				ContentID: content.ID,
				AssetID:   cast.ToInt64(mid),
				Sort:      int32(i),
				Role:      consts.ContentAssetRoleCover,
			})
		}
		// Main Media
		for i, mid := range form.MediaIDs {
			assets = append(assets, &models.ContentAsset{
				TenantID:  tid,
				UserID:    uid,
				ContentID: content.ID,
				AssetID:   cast.ToInt64(mid),
				Sort:      int32(i),
				Role:      consts.ContentAssetRoleMain,
			})
		}
		if len(assets) > 0 {
			if err := tx.ContentAsset.WithContext(ctx).Create(assets...); err != nil {
				return err
			}
		}

		// 3. Set Price
		price := &models.ContentPrice{
			TenantID:    tid,
			UserID:      uid,
			ContentID:   content.ID,
			PriceAmount: int64(form.Price * 100), // Convert to cents
			Currency:    consts.CurrencyCNY,
		}
		if err := tx.ContentPrice.WithContext(ctx).Create(price); err != nil {
			return err
		}

		return nil
	})
}

func (s *creator) UpdateContent(
	ctx context.Context,
	userID int64,
	id string,
	form *creator_dto.ContentUpdateForm,
) error {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return err
	}
	cid := cast.ToInt64(id)
	uid := userID

	return models.Q.Transaction(func(tx *models.Query) error {
		// 1. Check Ownership
		c, err := tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(cid), tx.Content.TenantID.Eq(tid)).First()
		if err != nil {
			return errorx.ErrRecordNotFound
		}

		// 2. Update Content
		_, err = tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(cid)).Updates(&models.Content{
			Title: form.Title,
			Genre: form.Genre,
			Key:   form.Key,
		})
		if err != nil {
			return err
		}

		// 3. Update Price
		// Check if price exists
		count, _ := tx.ContentPrice.WithContext(ctx).Where(tx.ContentPrice.ContentID.Eq(cid)).Count()
		newPrice := int64(form.Price * 100)
		if count > 0 {
			_, err = tx.ContentPrice.WithContext(ctx).
				Where(tx.ContentPrice.ContentID.Eq(cid)).
				UpdateSimple(tx.ContentPrice.PriceAmount.Value(newPrice))
		} else {
			err = tx.ContentPrice.WithContext(ctx).Create(&models.ContentPrice{
				TenantID:    tid,
				UserID:      c.UserID,
				ContentID:   cid,
				PriceAmount: newPrice,
				Currency:    consts.CurrencyCNY,
			})
		}
		if err != nil {
			return err
		}

		// 4. Update Assets (Full replacement strategy)
		_, err = tx.ContentAsset.WithContext(ctx).Where(tx.ContentAsset.ContentID.Eq(cid)).Delete()
		if err != nil {
			return err
		}

		var assets []*models.ContentAsset
		// Covers
		for i, mid := range form.CoverIDs {
			assets = append(assets, &models.ContentAsset{
				TenantID:  tid,
				UserID:    uid,
				ContentID: cid,
				AssetID:   cast.ToInt64(mid),
				Sort:      int32(i),
				Role:      consts.ContentAssetRoleCover,
			})
		}
		// Main Media
		for i, mid := range form.MediaIDs {
			assets = append(assets, &models.ContentAsset{
				TenantID:  tid,
				UserID:    uid,
				ContentID: cid,
				AssetID:   cast.ToInt64(mid),
				Sort:      int32(i),
				Role:      consts.ContentAssetRoleMain,
			})
		}
		if len(assets) > 0 {
			if err := tx.ContentAsset.WithContext(ctx).Create(assets...); err != nil {
				return err
			}
		}

		return nil
	})
}

func (s *creator) DeleteContent(ctx context.Context, userID int64, id string) error {
	cid := cast.ToInt64(id)
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return err
	}

	_, err = models.ContentQuery.WithContext(ctx).
		Where(models.ContentQuery.ID.Eq(cid), models.ContentQuery.TenantID.Eq(tid)).
		Delete()
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *creator) GetContent(ctx context.Context, userID int64, id string) (*creator_dto.ContentEditDTO, error) {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return nil, err
	}
	cid := cast.ToInt64(id)

	// Fetch Content with preloads
	var c models.Content
	err = models.ContentQuery.WithContext(ctx).
		Where(models.ContentQuery.ID.Eq(cid), models.ContentQuery.TenantID.Eq(tid)).
		UnderlyingDB().
		Preload("ContentAssets", func(db *gorm.DB) *gorm.DB {
			return db.Order("sort ASC")
		}).
		Preload("ContentAssets.Asset").
		First(&c).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errorx.ErrRecordNotFound
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	// Fetch Price
	var price float64
	cp, err := models.ContentPriceQuery.WithContext(ctx).Where(models.ContentPriceQuery.ContentID.Eq(cid)).First()
	if err == nil {
		price = float64(cp.PriceAmount) / 100.0
	}

	dto := &creator_dto.ContentEditDTO{
		ID:             cast.ToString(c.ID),
		Title:          c.Title,
		Genre:          c.Genre,
		Key:            c.Key,
		Description:    c.Description,
		Status:         string(c.Status),
		Price:          price,
		EnableTrial:    c.PreviewSeconds > 0,
		PreviewSeconds: int(c.PreviewSeconds),
		Assets:         make([]creator_dto.AssetDTO, 0),
	}

	for _, ca := range c.ContentAssets {
		if ca.Asset != nil {
			sizeBytes := ca.Asset.Meta.Data().Size
			sizeMB := float64(sizeBytes) / 1024.0 / 1024.0
			sizeStr := cast.ToString(float64(int(sizeMB*100))/100.0) + " MB"

			dto.Assets = append(dto.Assets, creator_dto.AssetDTO{
				ID:   cast.ToString(ca.AssetID),
				Role: string(ca.Role),
				Type: string(ca.Asset.Type),
				URL:  Common.GetAssetURL(ca.Asset.ObjectKey),
				Name: ca.Asset.ObjectKey, // Simple fallback
				Size: sizeStr,
				Sort: int(ca.Sort),
			})
		}
	}
	return dto, nil
}

func (s *creator) ListOrders(
	ctx context.Context,
	userID int64,
	filter *creator_dto.CreatorOrderListFilter,
) ([]creator_dto.Order, error) {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return nil, err
	}

	tbl, q := models.OrderQuery.QueryContext(ctx)
	q = q.Where(tbl.TenantID.Eq(tid))

	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(consts.OrderStatus(*filter.Status)))
	}
	// Keyword could match ID or other fields if needed

	list, err := q.Order(tbl.CreatedAt.Desc()).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	var data []creator_dto.Order
	for _, o := range list {
		data = append(data, creator_dto.Order{
			ID:         cast.ToString(o.ID),
			Status:     string(o.Status), // Enum conversion
			Amount:     float64(o.AmountPaid) / 100.0,
			CreateTime: o.CreatedAt.Format(time.RFC3339),
		})
	}
	return data, nil
}

func (s *creator) ProcessRefund(ctx context.Context, userID int64, id string, form *creator_dto.RefundForm) error {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return err
	}
	oid := cast.ToInt64(id)
	uid := userID // Creator ID

	// Fetch Order
	o, err := models.OrderQuery.WithContext(ctx).
		Where(models.OrderQuery.ID.Eq(oid), models.OrderQuery.TenantID.Eq(tid)).
		First()
	if err != nil {
		return errorx.ErrRecordNotFound
	}

	// Validate Status
	// Allow refunding 'refunding' orders. Or 'paid' if we treat this as "Initiate Refund".
	// Given "Action" (accept/reject), assume 'refunding'.
	if o.Status != consts.OrderStatusRefunding {
		return errorx.ErrStatusConflict.WithMsg("订单状态不是退款中")
	}

	if form.Action == "reject" {
		_, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).Updates(&models.Order{
			Status:       consts.OrderStatusPaid,
			RefundReason: form.Reason, // Store reject reason? Or clear it?
		})
		return err
	}

	if form.Action == "accept" {
		return models.Q.Transaction(func(tx *models.Query) error {
			// 1. Deduct Creator Balance
			// We credited Creator User Balance in Order.Pay. Now deduct it.
			info, err := tx.User.WithContext(ctx).
				Where(tx.User.ID.Eq(uid), tx.User.Balance.Gte(o.AmountPaid)).
				Update(tx.User.Balance, gorm.Expr("balance - ?", o.AmountPaid))
			if err != nil {
				return err
			}
			if info.RowsAffected == 0 {
				return errorx.ErrQuotaExceeded.WithMsg("余额不足，无法退款")
			}

			// 2. Credit Buyer Balance
			_, err = tx.User.WithContext(ctx).
				Where(tx.User.ID.Eq(o.UserID)).
				Update(tx.User.Balance, gorm.Expr("balance + ?", o.AmountPaid))
			if err != nil {
				return err
			}

			// 3. Update Order Status
			_, err = tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(oid)).Updates(&models.Order{
				Status:               consts.OrderStatusRefunded,
				RefundedAt:           time.Now(),
				RefundOperatorUserID: uid,
				RefundReason:         form.Reason,
			})
			if err != nil {
				return err
			}

			// 4. Revoke Content Access
			// Fetch order items to get content IDs
			items, _ := tx.OrderItem.WithContext(ctx).Where(tx.OrderItem.OrderID.Eq(oid)).Find()
			contentIDs := make([]int64, len(items))
			for i, item := range items {
				contentIDs[i] = item.ContentID
			}
			if len(contentIDs) > 0 {
				_, err = tx.ContentAccess.WithContext(ctx).
					Where(tx.ContentAccess.UserID.Eq(o.UserID), tx.ContentAccess.ContentID.In(contentIDs...)).
					UpdateSimple(tx.ContentAccess.Status.Value(consts.ContentAccessStatusRevoked))
				if err != nil {
					return err
				}
			}

			// 5. Create Tenant Ledger
			ledger := &models.TenantLedger{
				TenantID:       tid,
				UserID:         uid,
				OrderID:        oid,
				Type:           consts.TenantLedgerTypeCreditRefund,
				Amount:         o.AmountPaid,
				Remark:         "退款: " + form.Reason,
				OperatorUserID: uid,
				IdempotencyKey: uuid.NewString(),
			}
			if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
				return err
			}

			return nil
		})
	}

	return errorx.ErrBadRequest.WithMsg("无效的操作")
}

func (s *creator) GetSettings(ctx context.Context, userID int64) (*creator_dto.Settings, error) {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return nil, err
	}
	t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tid)).First()
	if err != nil {
		return nil, errorx.ErrRecordNotFound
	}
	// Extract from t.Config
	return &creator_dto.Settings{
		Name: t.Name,
		// Bio/Avatar from Config
	}, nil
}

func (s *creator) UpdateSettings(ctx context.Context, userID int64, form *creator_dto.Settings) error {
	return nil
}

func (s *creator) ListPayoutAccounts(ctx context.Context, userID int64) ([]creator_dto.PayoutAccount, error) {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return nil, err
	}

	list, err := models.PayoutAccountQuery.WithContext(ctx).Where(models.PayoutAccountQuery.TenantID.Eq(tid)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	var data []creator_dto.PayoutAccount
	for _, v := range list {
		data = append(data, creator_dto.PayoutAccount{
			ID:       cast.ToString(v.ID),
			Type:     v.Type,
			Name:     v.Name,
			Account:  v.Account,
			Realname: v.Realname,
		})
	}
	return data, nil
}

func (s *creator) AddPayoutAccount(ctx context.Context, userID int64, form *creator_dto.PayoutAccount) error {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return err
	}
	uid := userID

	pa := &models.PayoutAccount{
		TenantID: tid,
		UserID:   uid,
		Type:     form.Type,
		Name:     form.Name,
		Account:  form.Account,
		Realname: form.Realname,
	}
	if err := models.PayoutAccountQuery.WithContext(ctx).Create(pa); err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *creator) RemovePayoutAccount(ctx context.Context, userID int64, id string) error {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return err
	}
	pid := cast.ToInt64(id)

	_, err = models.PayoutAccountQuery.WithContext(ctx).
		Where(models.PayoutAccountQuery.ID.Eq(pid), models.PayoutAccountQuery.TenantID.Eq(tid)).
		Delete()
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *creator) Withdraw(ctx context.Context, userID int64, form *creator_dto.WithdrawForm) error {
	tid, err := s.getTenantID(ctx, userID)
	if err != nil {
		return err
	}
	uid := userID

	amount := int64(form.Amount * 100)
	if amount <= 0 {
		return errorx.ErrBadRequest.WithMsg("金额无效")
	}

	// Validate Payout Account
	_, err = models.PayoutAccountQuery.WithContext(ctx).
		Where(models.PayoutAccountQuery.ID.Eq(cast.ToInt64(form.AccountID)), models.PayoutAccountQuery.TenantID.Eq(tid)).
		First()
	if err != nil {
		return errorx.ErrRecordNotFound.WithMsg("收款账户不存在")
	}

	return models.Q.Transaction(func(tx *models.Query) error {
		// 1. Deduct Balance
		info, err := tx.User.WithContext(ctx).
			Where(tx.User.ID.Eq(uid), tx.User.Balance.Gte(amount)).
			Update(tx.User.Balance, gorm.Expr("balance - ?", amount))
		if err != nil {
			return err
		}
		if info.RowsAffected == 0 {
			return errorx.ErrQuotaExceeded.WithMsg("余额不足")
		}

		// 2. Create Order (Withdrawal)
		order := &models.Order{
			TenantID:       tid,
			UserID:         uid,
			Type:           consts.OrderTypeWithdrawal,
			Status:         consts.OrderStatusCreated, // Created = Pending Processing
			Currency:       consts.CurrencyCNY,
			AmountOriginal: amount,
			AmountPaid:     amount, // Actually Amount Withdrawn
			IdempotencyKey: uuid.NewString(),
			Snapshot:       types.NewJSONType(fields.OrdersSnapshot{}), // Can store account details here
		}
		if err := tx.Order.WithContext(ctx).Create(order); err != nil {
			return err
		}

		// 3. Create Tenant Ledger
		ledger := &models.TenantLedger{
			TenantID:       tid,
			UserID:         uid,
			OrderID:        order.ID,
			Type:           consts.TenantLedgerTypeCreditWithdrawal,
			Amount:         amount,
			Remark:         "提现申请",
			OperatorUserID: uid,
			IdempotencyKey: uuid.NewString(),
		}
		if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
			return err
		}

		return nil
	})
}

// Helpers

func (s *creator) getTenantID(ctx context.Context, userID int64) (int64, error) {
	if userID == 0 {
		return 0, errorx.ErrUnauthorized
	}
	uid := userID

	// Simple check: User owns tenant
	t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.UserID.Eq(uid)).First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return 0, errorx.ErrPermissionDenied.WithMsg("非创作者")
		}
		return 0, errorx.ErrDatabaseError.WithCause(err)
	}
	return t.ID, nil
}
