package activityapi

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	activityv1 "hyapp.local/api/proto/activity/v1"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
)

type agencyOpeningApplyBody struct {
	CommandID string `json:"command_id"`
}

type agencyOpeningStatusData struct {
	ServerTimeMS     int64                          `json:"server_time_ms"`
	Enabled          bool                           `json:"enabled"`
	Eligible         bool                           `json:"eligible"`
	Joined           bool                           `json:"joined"`
	IneligibleReason string                         `json:"ineligible_reason,omitempty"`
	Period           agencyOpeningPeriodData        `json:"period"`
	Qualification    agencyOpeningQualificationData `json:"qualification"`
	Rewards          []agencyOpeningRewardData      `json:"rewards"`
	RewardTiers      []agencyOpeningRewardData      `json:"reward_tiers"`
	Record           agencyOpeningRecordData        `json:"record"`
	Application      *agencyOpeningApplicationData  `json:"application,omitempty"`
	Leaderboard      []agencyOpeningApplicationData `json:"leaderboard,omitempty"`
	Rules            []string                       `json:"rules,omitempty"`
}

type agencyOpeningPeriodData struct {
	CycleID  string `json:"cycle_id,omitempty"`
	RegionID int64  `json:"region_id,omitempty"`
	Title    string `json:"title,omitempty"`
	Status   string `json:"status,omitempty"`
	StartMS  int64  `json:"start_ms,omitempty"`
	EndMS    int64  `json:"end_ms,omitempty"`
}

type agencyOpeningQualificationData struct {
	Identity        string `json:"agency_identity"`
	AgencyID        int64  `json:"agency_id,omitempty"`
	RegionID        int64  `json:"region_id,omitempty"`
	AgencyName      string `json:"agency_name,omitempty"`
	HostCount       int32  `json:"host_count"`
	HostRequirement int32  `json:"host_requirement"`
	OpeningStatus   string `json:"opening_status"`
	Eligible        bool   `json:"eligible"`
}

type agencyOpeningRewardData struct {
	RankNo             int32  `json:"rank_no"`
	TierNo             int32  `json:"tier_no"`
	ThresholdCoinSpent int64  `json:"threshold_coin_spent"`
	OpeningRevenue     string `json:"opening_revenue"`
	RewardCoins        int64  `json:"reward_coins"`
}

type agencyOpeningRecordData struct {
	GuildName      string `json:"guild_name,omitempty"`
	CreatedAt      string `json:"created_at,omitempty"`
	OpeningRevenue string `json:"opening_revenue,omitempty"`
	Reward         int64  `json:"reward"`
	ScoreEndMS     int64  `json:"score_end_ms,omitempty"`
}

type agencyOpeningApplicationData struct {
	ApplicationID       string `json:"application_id"`
	CycleID             string `json:"cycle_id"`
	RegionID            int64  `json:"region_id"`
	AgencyID            int64  `json:"agency_id"`
	AgencyOwnerUserID   int64  `json:"agency_owner_user_id"`
	AgencyName          string `json:"agency_name"`
	HostCount           int32  `json:"host_count"`
	AgencyCreatedAtMS   int64  `json:"agency_created_at_ms"`
	Status              string `json:"status"`
	ScoreCoinAmount     int64  `json:"score_coin_amount"`
	RewardRankNo        int32  `json:"reward_rank_no"`
	RewardThresholdCoin int64  `json:"reward_threshold_coin_spent"`
	RewardCoinAmount    int64  `json:"reward_coin_amount"`
	WalletCommandID     string `json:"wallet_command_id,omitempty"`
	WalletTransactionID string `json:"wallet_transaction_id,omitempty"`
	FailureReason       string `json:"failure_reason,omitempty"`
	AppliedAtMS         int64  `json:"applied_at_ms"`
	ApprovedAtMS        int64  `json:"approved_at_ms,omitempty"`
	ScoreStartMS        int64  `json:"score_start_ms,omitempty"`
	ScoreEndMS          int64  `json:"score_end_ms,omitempty"`
	ReviewedByAdminID   int64  `json:"reviewed_by_admin_id,omitempty"`
	GrantedAtMS         int64  `json:"granted_at_ms,omitempty"`
	UpdatedAtMS         int64  `json:"updated_at_ms,omitempty"`
}

// getAgencyOpeningStatus returns the H5 activity panel from activity-service facts.
func (h *Handler) getAgencyOpeningStatus(writer http.ResponseWriter, request *http.Request) {
	if h.agencyOpening == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	userID := auth.UserIDFromContext(request.Context())
	resp, err := h.agencyOpening.GetAgencyOpeningStatus(request.Context(), &activityv1.GetAgencyOpeningStatusRequest{
		Meta:   httpkit.ActivityMeta(request),
		UserId: userID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, agencyOpeningStatusFromProto(resp, nil))
}

// applyAgencyOpening only forwards the user action command_id;资格、周期和重复申请都由 activity-service 判定。
func (h *Handler) applyAgencyOpening(writer http.ResponseWriter, request *http.Request) {
	if h.agencyOpening == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	var body agencyOpeningApplyBody
	if err := httpkit.DecodeJSON(request.Body, &body); err != nil && !errors.Is(err, io.EOF) {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidJSON, "invalid request body")
		return
	}
	commandID := strings.TrimSpace(body.CommandID)
	if commandID == "" {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "command_id is required")
		return
	}
	userID := auth.UserIDFromContext(request.Context())
	applyResp, err := h.agencyOpening.ApplyAgencyOpening(request.Context(), &activityv1.ApplyAgencyOpeningRequest{
		Meta:      httpkit.ActivityMeta(request),
		UserId:    userID,
		CommandId: commandID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	statusResp, err := h.agencyOpening.GetAgencyOpeningStatus(request.Context(), &activityv1.GetAgencyOpeningStatusRequest{
		Meta:   httpkit.ActivityMeta(request),
		UserId: userID,
	})
	if err != nil {
		httpkit.WriteOK(writer, request, map[string]any{
			"created":     applyResp.GetCreated(),
			"joined":      true,
			"application": agencyOpeningApplicationFromProto(applyResp.GetApplication()),
		})
		return
	}
	data := agencyOpeningStatusFromProto(statusResp, applyResp.GetApplication())
	data.Joined = true
	httpkit.WriteOK(writer, request, data)
}

// listAgencyOpeningLeaderboard exposes the current or selected cycle榜单 for admin/H5 diagnostics.
func (h *Handler) listAgencyOpeningLeaderboard(writer http.ResponseWriter, request *http.Request) {
	if h.agencyOpening == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	userID := auth.UserIDFromContext(request.Context())
	statusResp, err := h.agencyOpening.GetAgencyOpeningStatus(request.Context(), &activityv1.GetAgencyOpeningStatusRequest{
		Meta:   httpkit.ActivityMeta(request),
		UserId: userID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	regionID := statusResp.GetAgency().GetRegionId()
	if regionID <= 0 {
		// 榜单不能在用户没有明确代理区域时退化成全区域读取，空结果比跨区域泄露更符合活动边界。
		httpkit.WriteOK(writer, request, map[string]any{"items": []agencyOpeningApplicationData{}, "total": int64(0)})
		return
	}
	cycleID := strings.TrimSpace(request.URL.Query().Get("cycle_id"))
	if cycleID == "" {
		cycleID = statusResp.GetCycle().GetCycleId()
	}
	resp, err := h.agencyOpening.ListAgencyOpeningLeaderboard(request.Context(), &activityv1.ListAgencyOpeningApplicationsRequest{
		Meta:     httpkit.ActivityMeta(request),
		CycleId:  cycleID,
		RegionId: regionID,
		Status:   strings.TrimSpace(request.URL.Query().Get("status")),
		Page:     int32(queryInt(request, "page", 1)),
		PageSize: int32(queryInt(request, "page_size", 50)),
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	items := make([]agencyOpeningApplicationData, 0, len(resp.GetApplications()))
	for _, item := range resp.GetApplications() {
		items = append(items, agencyOpeningApplicationFromProto(item))
	}
	httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": resp.GetTotal()})
}

func agencyOpeningStatusFromProto(item *activityv1.GetAgencyOpeningStatusResponse, applicationOverride *activityv1.AgencyOpeningApplication) agencyOpeningStatusData {
	if item == nil {
		return agencyOpeningStatusData{Rewards: []agencyOpeningRewardData{}, RewardTiers: []agencyOpeningRewardData{}, Leaderboard: []agencyOpeningApplicationData{}}
	}
	application := item.GetApplication()
	if applicationOverride != nil {
		application = applicationOverride
	}
	rewards := agencyOpeningRewardsFromProto(item.GetCycle().GetRewards())
	record := agencyOpeningRecordFromProto(application, item.GetAgency())
	return agencyOpeningStatusData{
		ServerTimeMS:     item.GetServerTimeMs(),
		Enabled:          item.GetEnabled(),
		Eligible:         item.GetEligible(),
		Joined:           item.GetJoined(),
		IneligibleReason: item.GetIneligibleReason(),
		Period: agencyOpeningPeriodData{
			CycleID:  item.GetCycle().GetCycleId(),
			RegionID: item.GetCycle().GetRegionId(),
			Title:    item.GetCycle().GetTitle(),
			Status:   item.GetCycle().GetStatus(),
			StartMS:  item.GetCycle().GetStartMs(),
			EndMS:    item.GetCycle().GetEndMs(),
		},
		Qualification: agencyOpeningQualificationFromProto(item, application),
		Rewards:       rewards,
		RewardTiers:   rewards,
		Record:        record,
		Application:   agencyOpeningApplicationPointerFromProto(application),
		Rules:         agencyOpeningRules(item.GetCycle()),
	}
}

func agencyOpeningQualificationFromProto(status *activityv1.GetAgencyOpeningStatusResponse, application *activityv1.AgencyOpeningApplication) agencyOpeningQualificationData {
	agency := status.GetAgency()
	hostCount := agency.GetHostCount()
	agencyID := agency.GetAgencyId()
	agencyName := agency.GetName()
	regionID := agency.GetRegionId()
	if application != nil {
		if hostCount == 0 {
			hostCount = application.GetHostCount()
		}
		if agencyID == 0 {
			agencyID = application.GetAgencyId()
		}
		if agencyName == "" {
			agencyName = application.GetAgencyName()
		}
		if regionID == 0 {
			regionID = application.GetRegionId()
		}
	}
	openingStatus := agencyOpeningStatusLabel(application)
	if !status.GetEnabled() && !status.GetJoined() {
		openingStatus = "Disabled"
	}
	return agencyOpeningQualificationData{
		Identity:        "Agency",
		AgencyID:        agencyID,
		RegionID:        regionID,
		AgencyName:      agencyName,
		HostCount:       hostCount,
		HostRequirement: status.GetCycle().GetMinHostCount(),
		OpeningStatus:   openingStatus,
		Eligible:        status.GetEligible() && !status.GetJoined(),
	}
}

func agencyOpeningRewardsFromProto(items []*activityv1.AgencyOpeningReward) []agencyOpeningRewardData {
	rewards := make([]agencyOpeningRewardData, 0, len(items))
	for _, item := range items {
		rewards = append(rewards, agencyOpeningRewardData{
			RankNo:             item.GetRankNo(),
			TierNo:             item.GetRankNo(),
			ThresholdCoinSpent: item.GetThresholdCoinSpent(),
			OpeningRevenue:     formatAgencyOpeningCoinLabel(item.GetThresholdCoinSpent()),
			RewardCoins:        item.GetRewardCoinAmount(),
		})
	}
	return rewards
}

func agencyOpeningRecordFromProto(application *activityv1.AgencyOpeningApplication, agency *activityv1.AgencyOpeningAgencySnapshot) agencyOpeningRecordData {
	if application == nil {
		return agencyOpeningRecordData{
			GuildName:      agency.GetName(),
			CreatedAt:      agencyOpeningDate(agency.GetAgencyCreatedAtMs()),
			OpeningRevenue: "0",
		}
	}
	return agencyOpeningRecordData{
		GuildName:      application.GetAgencyName(),
		CreatedAt:      agencyOpeningDate(application.GetAgencyCreatedAtMs()),
		OpeningRevenue: fmt.Sprintf("%d", application.GetScoreCoinAmount()),
		Reward:         application.GetRewardCoinAmount(),
		ScoreEndMS:     application.GetScoreEndMs(),
	}
}

func agencyOpeningApplicationPointerFromProto(item *activityv1.AgencyOpeningApplication) *agencyOpeningApplicationData {
	if item == nil {
		return nil
	}
	converted := agencyOpeningApplicationFromProto(item)
	return &converted
}

func agencyOpeningApplicationFromProto(item *activityv1.AgencyOpeningApplication) agencyOpeningApplicationData {
	if item == nil {
		return agencyOpeningApplicationData{}
	}
	return agencyOpeningApplicationData{
		ApplicationID:       item.GetApplicationId(),
		CycleID:             item.GetCycleId(),
		RegionID:            item.GetRegionId(),
		AgencyID:            item.GetAgencyId(),
		AgencyOwnerUserID:   item.GetAgencyOwnerUserId(),
		AgencyName:          item.GetAgencyName(),
		HostCount:           item.GetHostCount(),
		AgencyCreatedAtMS:   item.GetAgencyCreatedAtMs(),
		Status:              item.GetStatus(),
		ScoreCoinAmount:     item.GetScoreCoinAmount(),
		RewardRankNo:        item.GetRewardRankNo(),
		RewardThresholdCoin: item.GetRewardThresholdCoinSpent(),
		RewardCoinAmount:    item.GetRewardCoinAmount(),
		WalletCommandID:     item.GetWalletCommandId(),
		WalletTransactionID: item.GetWalletTransactionId(),
		FailureReason:       item.GetFailureReason(),
		AppliedAtMS:         item.GetAppliedAtMs(),
		ApprovedAtMS:        item.GetApprovedAtMs(),
		ScoreStartMS:        item.GetScoreStartMs(),
		ScoreEndMS:          item.GetScoreEndMs(),
		ReviewedByAdminID:   item.GetReviewedByAdminId(),
		GrantedAtMS:         item.GetGrantedAtMs(),
		UpdatedAtMS:         item.GetUpdatedAtMs(),
	}
}

func agencyOpeningStatusLabel(application *activityv1.AgencyOpeningApplication) string {
	if application == nil {
		return "Not Joined"
	}
	switch application.GetStatus() {
	case "applied":
		return "Pending Review"
	case "approved":
		return "Approved"
	case "pending":
		return "Pending Reward"
	case "granted":
		return "Rewarded"
	case "failed":
		return "Reward Failed"
	case "expired":
		return "Expired"
	default:
		return "Joined"
	}
}

func formatAgencyOpeningCoinLabel(value int64) string {
	if value <= 0 {
		return "0"
	}
	if value%1_000_000 == 0 {
		return fmt.Sprintf("%dM", value/1_000_000)
	}
	if value%1_000 == 0 {
		return fmt.Sprintf("%dK", value/1_000)
	}
	return strconv.FormatInt(value, 10)
}

func agencyOpeningRules(cycle *activityv1.AgencyOpeningCycle) []string {
	if cycle == nil {
		return nil
	}
	return []string{
		fmt.Sprintf("Agency must have more than %d hosts before applying.", cycle.GetMinHostCount()),
		"Opening revenue starts from admin approval and only counts the applicant user's room gift COIN revenue within the next 24 hours.",
		"If the 24-hour scoring window passes the activity end time, the activity end time is used as the cutoff.",
		"Reward tier is matched by opening revenue after the period ends; it is not based on Top ranking.",
		"Rewards are granted automatically after the period ends.",
	}
}

func agencyOpeningDate(ms int64) string {
	if ms <= 0 {
		return ""
	}
	return time.Unix(ms/1000, (ms%1000)*int64(time.Millisecond)).UTC().Format("2006-01-02")
}
