package grpc

import (
	"context"
	"encoding/json"
	"strings"

	activityv1 "hyapp.local/api/proto/activity/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	messagedomain "hyapp/services/activity-service/internal/domain/message"
	messageservice "hyapp/services/activity-service/internal/service/message"
)

// MessageServer adapts the message inbox owner to gRPC without mixing it into activity status APIs.
type MessageServer struct {
	activityv1.UnimplementedMessageInboxServiceServer

	svc *messageservice.Service
}

// NewMessageServer creates the gRPC adapter for system/activity inbox RPCs.
func NewMessageServer(svc *messageservice.Service) *MessageServer {
	return &MessageServer{svc: svc}
}

func (s *MessageServer) ListMessageTabs(ctx context.Context, req *activityv1.ListMessageTabsRequest) (*activityv1.ListMessageTabsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	sections, err := s.svc.ListMessageTabs(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	result := make([]*activityv1.MessageTabSection, 0, len(sections))
	for _, section := range sections {
		result = append(result, &activityv1.MessageTabSection{
			Section:     section.Section,
			Title:       section.Title,
			UnreadCount: section.UnreadCount,
			Source:      section.Source,
		})
	}
	return &activityv1.ListMessageTabsResponse{Sections: result}, nil
}

func (s *MessageServer) ListInboxMessages(ctx context.Context, req *activityv1.ListInboxMessagesRequest) (*activityv1.ListInboxMessagesResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	messages, nextPageToken, err := s.svc.ListInboxMessages(ctx, req.GetUserId(), req.GetSection(), req.GetPageSize(), req.GetPageToken())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	items := make([]*activityv1.InboxMessage, 0, len(messages))
	for _, message := range messages {
		items = append(items, inboxMessageProto(message))
	}
	return &activityv1.ListInboxMessagesResponse{Section: req.GetSection(), Items: items, NextPageToken: nextPageToken}, nil
}

func (s *MessageServer) MarkInboxMessageRead(ctx context.Context, req *activityv1.MarkInboxMessageReadRequest) (*activityv1.MarkInboxMessageReadResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	message, err := s.svc.MarkInboxMessageRead(ctx, req.GetUserId(), req.GetMessageId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.MarkInboxMessageReadResponse{MessageId: message.MessageID, Read: true, ReadAtMs: message.ReadAtMS}, nil
}

func (s *MessageServer) MarkInboxSectionRead(ctx context.Context, req *activityv1.MarkInboxSectionReadRequest) (*activityv1.MarkInboxSectionReadResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	count, err := s.svc.MarkInboxSectionRead(ctx, req.GetUserId(), req.GetSection())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.MarkInboxSectionReadResponse{Section: req.GetSection(), ReadCount: count}, nil
}

func (s *MessageServer) DeleteInboxMessage(ctx context.Context, req *activityv1.DeleteInboxMessageRequest) (*activityv1.DeleteInboxMessageResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	if err := s.svc.DeleteInboxMessage(ctx, req.GetUserId(), req.GetMessageId()); err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.DeleteInboxMessageResponse{MessageId: req.GetMessageId(), Deleted: true}, nil
}

func (s *MessageServer) CreateInboxMessage(ctx context.Context, req *activityv1.CreateInboxMessageRequest) (*activityv1.CreateInboxMessageResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	actions, err := inboxActionsFromProto(req.GetActions())
	if err != nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.InvalidArgument, "actions JSON is invalid"))
	}
	message, created, err := s.svc.CreateInboxMessage(ctx, messagedomain.CreateInput{
		TargetUserID:      req.GetTargetUserId(),
		Producer:          req.GetProducer(),
		ProducerEventID:   req.GetProducerEventId(),
		ProducerEventType: req.GetProducerEventType(),
		MessageType:       req.GetMessageType(),
		AggregateType:     req.GetAggregateType(),
		AggregateID:       req.GetAggregateId(),
		TemplateID:        req.GetTemplateId(),
		TemplateVersion:   req.GetTemplateVersion(),
		Title:             req.GetTitle(),
		Summary:           req.GetSummary(),
		Body:              req.GetBody(),
		IconURL:           req.GetIconUrl(),
		ImageURL:          req.GetImageUrl(),
		ImageURLs:         req.GetImageUrls(),
		RewardItems:       inboxRewardItemsFromProto(req.GetRewardItems()),
		Actions:           actions,
		ActionType:        req.GetActionType(),
		ActionParam:       req.GetActionParam(),
		Priority:          req.GetPriority(),
		SentAtMS:          req.GetSentAtMs(),
		ExpireAtMS:        req.GetExpireAtMs(),
		MetadataJSON:      req.GetMetadataJson(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.CreateInboxMessageResponse{MessageId: message.MessageID, Created: created}, nil
}

func (s *MessageServer) CreateFanoutJob(ctx context.Context, req *activityv1.CreateFanoutJobRequest) (*activityv1.CreateFanoutJobResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	job, created, err := s.svc.CreateFanoutJob(ctx, messagedomain.CreateFanoutInput{
		CommandID:            req.GetCommandId(),
		MessageType:          req.GetMessageType(),
		TargetScope:          req.GetTargetScope(),
		TargetFilterJSON:     req.GetTargetFilterJson(),
		TemplateSnapshotJSON: req.GetTemplateSnapshotJson(),
		BatchSize:            req.GetBatchSize(),
		CreatedBy:            req.GetCreatedBy(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.CreateFanoutJobResponse{JobId: job.JobID, Status: job.Status, Created: created}, nil
}

func inboxMessageProto(message messagedomain.InboxMessage) *activityv1.InboxMessage {
	imageURLs := message.ImageURLs
	if len(imageURLs) == 0 && strings.TrimSpace(message.ImageURL) != "" {
		imageURLs = []string{strings.TrimSpace(message.ImageURL)}
	}
	return &activityv1.InboxMessage{
		MessageId:   message.MessageID,
		Section:     message.MessageType,
		Title:       message.Title,
		Summary:     message.Summary,
		Body:        message.Body,
		IconUrl:     message.IconURL,
		ImageUrl:    message.ImageURL,
		ImageUrls:   imageURLs,
		RewardItems: inboxRewardItemsToProto(message.RewardItems),
		Actions:     inboxActionsToProto(message.Actions),
		ActionType:  message.ActionType,
		ActionParam: message.ActionParam,
		Read:        message.Read(),
		SentAtMs:    message.SentAtMS,
	}
}

func inboxActionsFromProto(items []*activityv1.InboxAction) ([]messagedomain.InboxAction, error) {
	result := make([]messagedomain.InboxAction, 0, len(items))
	for _, item := range items {
		if item == nil {
			continue
		}
		query := map[string]any{}
		if raw := strings.TrimSpace(item.GetApi().GetQueryJson()); raw != "" {
			if err := json.Unmarshal([]byte(raw), &query); err != nil {
				return nil, err
			}
		}
		var body any
		if raw := strings.TrimSpace(item.GetApi().GetBodyJson()); raw != "" {
			if err := json.Unmarshal([]byte(raw), &body); err != nil {
				return nil, err
			}
		}
		result = append(result, messagedomain.InboxAction{
			Action: item.GetAction(),
			Label:  item.GetLabel(),
			Style:  item.GetStyle(),
			API: messagedomain.InboxActionAPI{
				Method: item.GetApi().GetMethod(),
				Path:   item.GetApi().GetPath(),
				Query:  query,
				Body:   body,
			},
		})
	}
	return result, nil
}

func inboxActionsToProto(items []messagedomain.InboxAction) []*activityv1.InboxAction {
	result := make([]*activityv1.InboxAction, 0, len(items))
	for _, item := range items {
		queryJSON := ""
		if len(item.API.Query) > 0 {
			if encoded, err := json.Marshal(item.API.Query); err == nil {
				queryJSON = string(encoded)
			}
		}
		bodyJSON := ""
		if item.API.Body != nil {
			if encoded, err := json.Marshal(item.API.Body); err == nil {
				bodyJSON = string(encoded)
			}
		}
		result = append(result, &activityv1.InboxAction{
			Action: item.Action,
			Label:  item.Label,
			Style:  item.Style,
			Api: &activityv1.InboxActionAPI{
				Method:    item.API.Method,
				Path:      item.API.Path,
				QueryJson: queryJSON,
				BodyJson:  bodyJSON,
			},
		})
	}
	return result
}

func inboxRewardItemsFromProto(items []*activityv1.InboxRewardItem) []messagedomain.RewardItem {
	result := make([]messagedomain.RewardItem, 0, len(items))
	for _, item := range items {
		if item == nil {
			continue
		}
		result = append(result, messagedomain.RewardItem{
			RewardType:   item.GetRewardType(),
			Name:         item.GetName(),
			ImageURL:     item.GetImageUrl(),
			Quantity:     item.GetQuantity(),
			DurationDays: item.GetDurationDays(),
		})
	}
	return result
}

func inboxRewardItemsToProto(items []messagedomain.RewardItem) []*activityv1.InboxRewardItem {
	result := make([]*activityv1.InboxRewardItem, 0, len(items))
	for _, item := range items {
		result = append(result, &activityv1.InboxRewardItem{
			RewardType:   item.RewardType,
			Name:         item.Name,
			ImageUrl:     item.ImageURL,
			Quantity:     item.Quantity,
			DurationDays: item.DurationDays,
		})
	}
	return result
}
