package cprelation

import (
	"context"
	"database/sql"
	"fmt"
	"slices"
	"strconv"
	"strings"
	"time"
)

const (
	cpListDefaultPageSize = 50
	cpListMaxPageSize     = 100
	dayMS                 = int64(24 * 60 * 60 * 1000)
)

var (
	relationshipStatuses = []string{"active", "ended"}
	applicationStatuses  = []string{"pending", "accepted", "rejected", "expired", "blocked"}
)

type cpListOptions struct {
	Page         int
	PageSize     int
	Status       string
	RelationType string
}

type cpUserDTO struct {
	UserID        int64  `json:"userId,string"`
	DisplayUserID string `json:"displayUserId"`
	Username      string `json:"username"`
	Avatar        string `json:"avatar"`
}

type cpGiftSnapshotDTO struct {
	GiftID           string `json:"giftId"`
	GiftName         string `json:"giftName"`
	GiftIconURL      string `json:"giftIconUrl"`
	GiftAnimationURL string `json:"giftAnimationUrl"`
	GiftCount        int64  `json:"giftCount"`
	GiftValue        int64  `json:"giftValue"`
	BillingReceiptID string `json:"billingReceiptId"`
}

type cpRelationshipDTO struct {
	RelationshipID string             `json:"relationshipId"`
	RelationType   string             `json:"relationType"`
	Status         string             `json:"status"`
	UserA          cpUserDTO          `json:"userA"`
	UserB          cpUserDTO          `json:"userB"`
	IntimacyValue  int64              `json:"intimacyValue"`
	Level          int32              `json:"level"`
	DurationDays   int64              `json:"durationDays"`
	Gift           *cpGiftSnapshotDTO `json:"gift,omitempty"`
	FormedAtMS     int64              `json:"formedAtMs"`
	UpdatedAtMS    int64              `json:"updatedAtMs"`
	EndedAtMS      int64              `json:"endedAtMs"`
}

type cpRelationshipPageDTO struct {
	Items        []cpRelationshipDTO `json:"items"`
	Page         int                 `json:"page"`
	PageSize     int                 `json:"pageSize"`
	Total        int64               `json:"total"`
	ServerTimeMS int64               `json:"serverTimeMs"`
}

type cpApplicationDTO struct {
	ApplicationID string            `json:"applicationId"`
	RelationType  string            `json:"relationType"`
	Status        string            `json:"status"`
	Requester     cpUserDTO         `json:"requester"`
	Target        cpUserDTO         `json:"target"`
	Gift          cpGiftSnapshotDTO `json:"gift"`
	RoomID        string            `json:"roomId"`
	RoomRegionID  int64             `json:"roomRegionId"`
	CreatedAtMS   int64             `json:"createdAtMs"`
	UpdatedAtMS   int64             `json:"updatedAtMs"`
	ExpiresAtMS   int64             `json:"expiresAtMs"`
	DecidedAtMS   int64             `json:"decidedAtMs"`
}

type cpApplicationPageDTO struct {
	Items        []cpApplicationDTO `json:"items"`
	Page         int                `json:"page"`
	PageSize     int                `json:"pageSize"`
	Total        int64              `json:"total"`
	ServerTimeMS int64              `json:"serverTimeMs"`
}

// ListRelationships 给管理端按全站维度分页读取关系；这里不能复用 App 侧按单用户读取的仓储，否则会漏掉“所有用户关系”这个运营视角。
func (s *Service) ListRelationships(ctx context.Context, appCode string, options cpListOptions) (cpRelationshipPageDTO, error) {
	options = normalizeCPListOptions(options)
	nowMS := time.Now().UTC().UnixMilli()
	whereSQL, args, err := relationshipListWhere(appCode, options)
	if err != nil {
		return cpRelationshipPageDTO{}, err
	}
	if s == nil || s.db == nil {
		return cpRelationshipPageDTO{}, errorsUserDBNotConfigured()
	}
	total, err := s.countRows(ctx, "SELECT COUNT(*) FROM user_cp_relationships r WHERE "+whereSQL, args...)
	if err != nil {
		return cpRelationshipPageDTO{}, err
	}
	queryArgs := append(append([]any{}, args...), options.PageSize, cpListOffset(options))
	rows, err := s.db.QueryContext(ctx, `
		SELECT
			r.relationship_id, r.relation_type, r.status,
			r.user_a_id, COALESCE(user_a.current_display_user_id, ''), COALESCE(user_a.username, ''), COALESCE(user_a.avatar, ''),
			r.user_b_id, COALESCE(user_b.current_display_user_id, ''), COALESCE(user_b.username, ''), COALESCE(user_b.avatar, ''),
			r.intimacy_value, r.level_no, r.formed_at_ms, r.updated_at_ms, r.ended_at_ms,
			COALESCE(source.gift_id, ''), COALESCE(source.gift_name, ''), COALESCE(source.gift_icon_url, ''),
			COALESCE(source.gift_animation_url, ''), COALESCE(source.gift_count, 0), COALESCE(source.gift_value, 0),
			COALESCE(source.billing_receipt_id, '')
		FROM user_cp_relationships r
		LEFT JOIN users user_a ON user_a.app_code = r.app_code AND user_a.user_id = r.user_a_id
		LEFT JOIN users user_b ON user_b.app_code = r.app_code AND user_b.user_id = r.user_b_id
		LEFT JOIN user_cp_applications source ON source.app_code = r.app_code AND source.application_id = r.source_application_id
		WHERE `+whereSQL+`
		ORDER BY r.formed_at_ms DESC, r.relationship_id DESC
		LIMIT ? OFFSET ?`, queryArgs...)
	if err != nil {
		return cpRelationshipPageDTO{}, err
	}
	defer rows.Close()
	items, err := scanRelationshipRows(rows, nowMS)
	if err != nil {
		return cpRelationshipPageDTO{}, err
	}
	return cpRelationshipPageDTO{Items: items, Page: options.Page, PageSize: options.PageSize, Total: total, ServerTimeMS: nowMS}, nil
}

// ListApplications 读取所有用户的申请记录；读取前先沿用 App 侧懒过期语义，避免后台把已过期申请继续展示为 pending。
func (s *Service) ListApplications(ctx context.Context, appCode string, options cpListOptions) (cpApplicationPageDTO, error) {
	options = normalizeCPListOptions(options)
	nowMS := time.Now().UTC().UnixMilli()
	whereSQL, args, err := applicationListWhere(appCode, options)
	if err != nil {
		return cpApplicationPageDTO{}, err
	}
	if s == nil || s.db == nil {
		return cpApplicationPageDTO{}, errorsUserDBNotConfigured()
	}
	if err := s.expirePendingApplications(ctx, appCode, nowMS); err != nil {
		return cpApplicationPageDTO{}, err
	}
	total, err := s.countRows(ctx, "SELECT COUNT(*) FROM user_cp_applications a WHERE "+whereSQL, args...)
	if err != nil {
		return cpApplicationPageDTO{}, err
	}
	queryArgs := append(append([]any{}, args...), options.PageSize, cpListOffset(options))
	rows, err := s.db.QueryContext(ctx, `
		SELECT
			a.application_id, a.relation_type, a.status,
			a.requester_user_id, COALESCE(requester.current_display_user_id, ''), COALESCE(requester.username, ''), COALESCE(requester.avatar, ''),
			a.target_user_id, COALESCE(target.current_display_user_id, ''), COALESCE(target.username, ''), COALESCE(target.avatar, ''),
			a.gift_id, a.gift_name, a.gift_icon_url, a.gift_animation_url, a.gift_count, a.gift_value, a.billing_receipt_id,
			a.room_id, a.room_region_id, a.created_at_ms, a.updated_at_ms, a.expires_at_ms, a.decided_at_ms
		FROM user_cp_applications a
		LEFT JOIN users requester ON requester.app_code = a.app_code AND requester.user_id = a.requester_user_id
		LEFT JOIN users target ON target.app_code = a.app_code AND target.user_id = a.target_user_id
		WHERE `+whereSQL+`
		ORDER BY a.created_at_ms DESC, a.application_id DESC
		LIMIT ? OFFSET ?`, queryArgs...)
	if err != nil {
		return cpApplicationPageDTO{}, err
	}
	defer rows.Close()
	items, err := scanApplicationRows(rows)
	if err != nil {
		return cpApplicationPageDTO{}, err
	}
	return cpApplicationPageDTO{Items: items, Page: options.Page, PageSize: options.PageSize, Total: total, ServerTimeMS: nowMS}, nil
}

func (s *Service) expirePendingApplications(ctx context.Context, appCode string, nowMS int64) error {
	_, err := s.db.ExecContext(ctx, `
		UPDATE user_cp_applications
		SET status = 'expired', updated_at_ms = ?
		WHERE app_code = ? AND status = 'pending' AND expires_at_ms > 0 AND expires_at_ms <= ?`,
		nowMS, appCode, nowMS,
	)
	return err
}

func scanRelationshipRows(rows *sql.Rows, nowMS int64) ([]cpRelationshipDTO, error) {
	items := []cpRelationshipDTO{}
	for rows.Next() {
		var item cpRelationshipDTO
		var gift cpGiftSnapshotDTO
		if err := rows.Scan(
			&item.RelationshipID, &item.RelationType, &item.Status,
			&item.UserA.UserID, &item.UserA.DisplayUserID, &item.UserA.Username, &item.UserA.Avatar,
			&item.UserB.UserID, &item.UserB.DisplayUserID, &item.UserB.Username, &item.UserB.Avatar,
			&item.IntimacyValue, &item.Level, &item.FormedAtMS, &item.UpdatedAtMS, &item.EndedAtMS,
			&gift.GiftID, &gift.GiftName, &gift.GiftIconURL, &gift.GiftAnimationURL, &gift.GiftCount, &gift.GiftValue, &gift.BillingReceiptID,
		); err != nil {
			return nil, err
		}
		item.UserA = normalizeCPUser(item.UserA)
		item.UserB = normalizeCPUser(item.UserB)
		item.DurationDays = relationshipDurationDays(item.FormedAtMS, item.EndedAtMS, nowMS)
		if !gift.empty() {
			item.Gift = &gift
		}
		items = append(items, item)
	}
	return items, rows.Err()
}

func scanApplicationRows(rows *sql.Rows) ([]cpApplicationDTO, error) {
	items := []cpApplicationDTO{}
	for rows.Next() {
		var item cpApplicationDTO
		if err := rows.Scan(
			&item.ApplicationID, &item.RelationType, &item.Status,
			&item.Requester.UserID, &item.Requester.DisplayUserID, &item.Requester.Username, &item.Requester.Avatar,
			&item.Target.UserID, &item.Target.DisplayUserID, &item.Target.Username, &item.Target.Avatar,
			&item.Gift.GiftID, &item.Gift.GiftName, &item.Gift.GiftIconURL, &item.Gift.GiftAnimationURL, &item.Gift.GiftCount, &item.Gift.GiftValue, &item.Gift.BillingReceiptID,
			&item.RoomID, &item.RoomRegionID, &item.CreatedAtMS, &item.UpdatedAtMS, &item.ExpiresAtMS, &item.DecidedAtMS,
		); err != nil {
			return nil, err
		}
		item.Requester = normalizeCPUser(item.Requester)
		item.Target = normalizeCPUser(item.Target)
		items = append(items, item)
	}
	return items, rows.Err()
}

func relationshipListWhere(appCode string, options cpListOptions) (string, []any, error) {
	where := []string{"r.app_code = ?"}
	args := []any{appCode}
	if err := appendRelationFilter(&where, &args, "r", options.RelationType); err != nil {
		return "", nil, err
	}
	if err := appendStatusFilter(&where, &args, "r", options.Status, relationshipStatuses, "关系状态不正确"); err != nil {
		return "", nil, err
	}
	return strings.Join(where, " AND "), args, nil
}

func applicationListWhere(appCode string, options cpListOptions) (string, []any, error) {
	where := []string{"a.app_code = ?"}
	args := []any{appCode}
	if err := appendRelationFilter(&where, &args, "a", options.RelationType); err != nil {
		return "", nil, err
	}
	if err := appendStatusFilter(&where, &args, "a", options.Status, applicationStatuses, "申请状态不正确"); err != nil {
		return "", nil, err
	}
	return strings.Join(where, " AND "), args, nil
}

func appendRelationFilter(where *[]string, args *[]any, alias string, value string) error {
	relationType := normalizeCPFilterValue(value)
	if relationType == "" {
		return nil
	}
	if !slices.Contains(relationTypes, relationType) {
		return fmt.Errorf("%w: 关系类型不正确", ErrInvalidArgument)
	}
	*where = append(*where, alias+".relation_type = ?")
	*args = append(*args, relationType)
	return nil
}

func appendStatusFilter(where *[]string, args *[]any, alias string, value string, allowed []string, message string) error {
	status := normalizeCPFilterValue(value)
	if status == "" {
		return nil
	}
	if !slices.Contains(allowed, status) {
		return fmt.Errorf("%w: %s", ErrInvalidArgument, message)
	}
	*where = append(*where, alias+".status = ?")
	*args = append(*args, status)
	return nil
}

func normalizeCPListOptions(options cpListOptions) cpListOptions {
	if options.Page < 1 {
		options.Page = 1
	}
	if options.PageSize < 1 {
		options.PageSize = cpListDefaultPageSize
	}
	if options.PageSize > cpListMaxPageSize {
		options.PageSize = cpListMaxPageSize
	}
	options.Status = normalizeCPFilterValue(options.Status)
	options.RelationType = normalizeCPFilterValue(options.RelationType)
	return options
}

func normalizeCPFilterValue(value string) string {
	value = strings.ToLower(strings.TrimSpace(value))
	if value == "all" {
		return ""
	}
	return value
}

func cpListOffset(options cpListOptions) int {
	return (options.Page - 1) * options.PageSize
}

func (s *Service) countRows(ctx context.Context, query string, args ...any) (int64, error) {
	var total int64
	if err := s.db.QueryRowContext(ctx, query, args...).Scan(&total); err != nil {
		return 0, err
	}
	return total, nil
}

func normalizeCPUser(user cpUserDTO) cpUserDTO {
	if strings.TrimSpace(user.DisplayUserID) == "" && user.UserID > 0 {
		user.DisplayUserID = strconv.FormatInt(user.UserID, 10)
	}
	return user
}

func relationshipDurationDays(formedAtMS int64, endedAtMS int64, nowMS int64) int64 {
	if formedAtMS <= 0 {
		return 0
	}
	endAtMS := nowMS
	if endedAtMS > 0 {
		endAtMS = endedAtMS
	}
	if endAtMS < formedAtMS {
		return 0
	}
	// 运营侧“天数”按自然关系天展示：刚建立也算第 1 天，避免表格出现 0 天这种难以理解的状态。
	return ((endAtMS - formedAtMS) / dayMS) + 1
}

func (gift cpGiftSnapshotDTO) empty() bool {
	return strings.TrimSpace(gift.GiftID) == "" &&
		strings.TrimSpace(gift.GiftName) == "" &&
		strings.TrimSpace(gift.GiftIconURL) == "" &&
		strings.TrimSpace(gift.GiftAnimationURL) == "" &&
		strings.TrimSpace(gift.BillingReceiptID) == "" &&
		gift.GiftCount == 0 &&
		gift.GiftValue == 0
}

func errorsUserDBNotConfigured() error {
	return fmt.Errorf("user db is not configured")
}
