package notification

import (
	"context"
	"errors"
	"slices"
	"strings"

	"tm/internal/customer"
	"tm/internal/user"
	"tm/pkg/logger"
	"tm/pkg/notification"
	"tm/pkg/response"
)

// NotificationService defines the interface for notification business logic
type Service interface {
	Send(ctx context.Context, req *NotificationRequest) error
	GetNotifications(ctx context.Context, req *SearchForm, pagination *response.Pagination) (*NotificationListResponse, error)
	GetNotification(ctx context.Context, notificationID string) (*NotificationResponse, error)
	MarkSeen(ctx context.Context, notificationID, userID string) error
	AllMarkSeen(ctx context.Context, userID string) error
	getUsers(ctx context.Context, userIDs []string) ([]notificationRecipient, error)
	getCustomers(ctx context.Context, values []string, target string) ([]notificationRecipient, error)
}

// notificationService implements NotificationService interface
type notificationService struct {
	sdk             notification.SDK
	repository      Repository
	userService     user.Service
	customerService customer.Service
	logger          logger.Logger
}

// NewService creates a new notification service
func NewService(
	sdk notification.SDK,
	repository Repository,
	userService user.Service,
	customerService customer.Service,
	logger logger.Logger,
) Service {
	return &notificationService{
		sdk:             sdk,
		repository:      repository,
		userService:     userService,
		customerService: customerService,
		logger:          logger,
	}
}

// Send sends a multiple notification
func (s *notificationService) Send(ctx context.Context, req *NotificationRequest) error {
	image := ""
	if req.Image != nil {
		image = *req.Image
	}

	link := ""
	if req.Link != nil {
		link = *req.Link
	}

	s.logger.Info("Sending notification", map[string]interface{}{
		"recipient": req.Recipient,
		"type":      req.Type,
		"priority":  req.Priority,
		"link":      link,
		"image":     image,
	})

	if req.Target == TargetAudienceAllUsers || req.Target == TargetAudienceSpecificUsers {
		recipients, err := s.getUsers(ctx, req.Recipient)
		if err != nil {
			return err
		}
		for _, recipient := range recipients {
			s.sendToRecipient(ctx, recipient, req, link, image)
		}
	} else {
		recipients, err := s.getCustomers(ctx, req.Recipient, string(req.Target))
		if err != nil {
			return err
		}
		for _, recipient := range recipients {
			s.sendToRecipient(ctx, recipient, req, link, image)
		}
	}

	s.logger.Info("Notification sent", map[string]interface{}{
		"recipient": req.Recipient,
		"channels":  req.Channels,
		"target":    req.Target,
	})

	return nil
}

func (s *notificationService) sendToRecipient(ctx context.Context, recipient notificationRecipient, req *NotificationRequest, link, image string) {
	if err := s.persistNotification(ctx, recipient, req, link, image); err != nil {
		s.logger.Error("Failed to persist in-app notification", map[string]interface{}{
			"error":   err.Error(),
			"user_id": recipient.UserID,
		})
	}

	metadata := map[string]any{
		"tender": strings.TrimSpace(req.Tender),
	}

	if slices.Contains(req.Channels, DeliveryChannelPush) {
		for _, v := range recipient.DeviceTokens {
			s.sdk.SendNotification(ctx, &notification.NotificationRequest{
				UserID:      recipient.UserID,
				Title:       req.Title,
				Message:     req.Description,
				Type:        string(req.Type),
				Priority:    string(req.Priority),
				EventType:   notification.EventTypePush,
				Link:        link,
				Image:       image,
				Metadata:    metadata,
				Methods: notification.NotificationMethods{
					Push: v,
				},
				ScheduledAt: req.ScheduleAt,
			})
		}
	}

	if slices.Contains(req.Channels, DeliveryChannelEmail) && recipient.Email != "" {
		s.sdk.SendNotification(ctx, &notification.NotificationRequest{
			UserID:      recipient.UserID,
			Title:       req.Title,
			Message:     req.Description,
			Type:        string(req.Type),
			Priority:    string(req.Priority),
			EventType:   notification.EventTypeEmail,
			Link:        link,
			Image:       image,
			Metadata:    metadata,
			Methods: notification.NotificationMethods{
				Email: recipient.Email,
			},
			ScheduledAt: req.ScheduleAt,
		})
	}
}

func (s *notificationService) persistNotification(ctx context.Context, recipient notificationRecipient, req *NotificationRequest, link, image string) error {
	methods := make(map[string]string)
	if slices.Contains(req.Channels, DeliveryChannelEmail) && recipient.Email != "" {
		methods["email"] = recipient.Email
	}
	if slices.Contains(req.Channels, DeliveryChannelPush) && len(recipient.DeviceTokens) > 0 {
		methods["push"] = recipient.DeviceTokens[0]
	}

	status := string(DeliveryStatusSent)
	if req.ScheduleAt > 0 {
		status = string(DeliveryStatusPending)
	}

	return s.repository.Create(ctx, &Notification{
		UserID:      recipient.UserID,
		Title:       req.Title,
		Message:     req.Description,
		Link:        link,
		Image:       image,
		Priority:    string(req.Priority),
		Type:        string(req.Type),
		Status:      status,
		Methods:     methods,
		Metadata: map[string]any{
			"tender": strings.TrimSpace(req.Tender),
		},
		ScheduleAt:  req.ScheduleAt,
		ScheduledAt: req.ScheduleAt,
		IsScheduled: req.ScheduleAt > 0,
		Seen:        false,
	})
}

func (s *notificationService) GetNotifications(ctx context.Context, req *SearchForm, pagination *response.Pagination) (*NotificationListResponse, error) {
	s.logger.Info("Getting notifications", map[string]interface{}{
		"status":     req.Status,
		"method":     req.Method,
		"event_type": req.EventType,
		"type":       req.Type,
		"recipient":  req.Recipient,
		"seen":       req.Seen,
		"priority":   req.Priority,
		"limit":      pagination.Limit,
		"offset":     pagination.Offset,
	})

	page, err := s.repository.GetByUserID(ctx, req.Recipient, req.Status, req.Seen, req.Priority, req.EventType, req.Type, pagination)
	if err != nil {
		s.logger.Error("Failed to get notifications", map[string]interface{}{
			"error": err.Error(),
		})
		return nil, err
	}

	notificationsResponse := make([]*NotificationResponse, 0)
	for _, notification := range page.Items {
		usr, _ := s.userService.GetUserByID(ctx, notification.UserID)
		cust, _ := s.customerService.GetByID(ctx, notification.UserID)

		var recipient any
		if usr != nil {
			recipient = usr
		} else if cust != nil {
			recipient = cust
		}

		// Handle Link pointer - return nil if empty
		var linkPtr *string
		if notification.Link != "" {
			linkPtr = &notification.Link
		}

		// Handle Image pointer - return nil if empty
		var imagePtr *string
		if notification.Image != "" {
			imagePtr = &notification.Image
		}

		notificationsResponse = append(notificationsResponse, &NotificationResponse{
			ID:          notification.GetID(),
			UserID:      notification.UserID,
			Recipient:   recipient,
			Title:       notification.Title,
			Message:     notification.Message,
			Link:        linkPtr,
			Priority:    notification.Priority,
			Type:        notification.Type,
			EventType:   notification.EventType,
			Status:      notification.Status,
			Methods:     notification.Methods,
			Metadata:    notification.Metadata,
			ScheduleAt:  notification.ScheduleAt,
			IsScheduled: notification.IsScheduled,
			Image:       imagePtr,
			Seen:        notification.Seen,
			SeenAt:      notification.SeenAt,
			ScheduledAt: notification.ScheduledAt,
			CreatedAt:   notification.CreatedAt,
			UpdatedAt:   notification.UpdatedAt,
		})
	}

	return &NotificationListResponse{
		Notifications: notificationsResponse,
		Meta: pagination.ListMeta(page.TotalCount, page.NextCursor, page.HasMore, page.PageOffset),
	}, nil
}

func (s *notificationService) GetNotification(ctx context.Context, notificationID string) (*NotificationResponse, error) {
	s.logger.Info("Getting notification", map[string]interface{}{
		"notification_id": notificationID,
	})

	notification, err := s.repository.GetByID(ctx, notificationID)
	if err != nil {
		s.logger.Error("Failed to get notification", map[string]interface{}{
			"error":           err.Error(),
			"notification_id": notificationID,
		})
		return nil, err
	}

	// Convert to detailed response format
	var linkPtr *string
	if notification.Link != "" {
		linkPtr = &notification.Link
	}

	var imagePtr *string
	if notification.Image != "" {
		imagePtr = &notification.Image
	}

	usr, _ := s.userService.GetUserByID(ctx, notification.UserID)
	cust, _ := s.customerService.GetByID(ctx, notification.UserID)

	var recipient any
	if usr != nil {
		recipient = usr
	} else if cust != nil {
		recipient = cust
	}

	detailedResponse := &NotificationResponse{
		ID:          notification.GetID(),
		UserID:      notification.UserID,
		Recipient:   recipient,
		Title:       notification.Title,
		Message:     notification.Message,
		Link:        linkPtr,
		Image:       imagePtr,
		Type:        notification.Type,
		Priority:    notification.Priority,
		EventType:   notification.EventType,
		CreatedAt:   notification.CreatedAt,
		UpdatedAt:   notification.UpdatedAt,
		Methods:     notification.Methods,
		Metadata:    notification.Metadata,
		Status:      notification.Status,
		Seen:        notification.Seen,
		SeenAt:      notification.SeenAt,
		ScheduledAt: notification.ScheduledAt,
		IsScheduled: notification.IsScheduled,
	}

	return detailedResponse, nil
}

func (s *notificationService) MarkSeen(ctx context.Context, notificationID, userID string) error {
	s.logger.Info("Marking notification as seen", map[string]interface{}{
		"notification_id": notificationID,
	})

	err := s.repository.MarkAsSeen(ctx, notificationID, userID)
	if err != nil {
		s.logger.Error("Failed to mark notification as seen", map[string]interface{}{
			"error":           err.Error(),
			"notification_id": notificationID,
		})
		return err
	}
	return nil
}

func (s *notificationService) AllMarkSeen(ctx context.Context, userID string) error {
	s.logger.Info("Marking all notifications as seen", map[string]interface{}{
		"user_id": userID,
	})

	err := s.repository.MarkAllAsSeen(ctx, userID)
	if err != nil {
		s.logger.Error("Failed to mark all notifications as seen", map[string]interface{}{
			"error":   err.Error(),
			"user_id": userID,
		})
		return err
	}
	return nil
}

func (s *notificationService) getUsers(ctx context.Context, userIDs []string) ([]notificationRecipient, error) {
	recipients := make([]notificationRecipient, 0)
	offset := 0
	for {
		var users *user.UserListResponse
		var err error
		if len(userIDs) > 0 {
			users, err = s.userService.GetUsersByIDs(ctx, userIDs)
			if err != nil {
				return nil, err
			}
		} else {
			users, err = s.userService.Search(ctx, &user.SearchUsersForm{}, &response.Pagination{Limit: 100, Offset: offset})
			if err != nil {
				return nil, err
			}
			offset += 100
			if len(users.Users) == 0 {
				break
			}
		}

		for _, user := range users.Users {
			recipients = append(recipients, notificationRecipient{
				UserID:       user.ID,
				Email:        user.Email,
				DeviceTokens: user.DeviceToken,
			})
		}

		if len(userIDs) > 0 || len(users.Users) == 0 {
			break
		}
	}

	if len(recipients) == 0 {
		return nil, errors.New("no users found")
	}

	return recipients, nil
}

func (s *notificationService) getCustomers(ctx context.Context, values []string, target string) ([]notificationRecipient, error) {
	recipients := make([]notificationRecipient, 0)

	if target == string(TargetAudienceSpecificRole) {
		if len(values) == 0 {
			return nil, errors.New("no role provided")
		}

		customers, err := s.customerService.GetCustomersByRole(ctx, values[0])
		if err != nil {
			return nil, err
		}
		for _, customer := range customers {
			recipients = append(recipients, notificationRecipient{
				UserID:       customer.ID,
				Email:        customer.Email,
				DeviceTokens: customer.DeviceToken,
			})
		}
	} else if target == string(TargetAudienceSpecificCompany) {
		customers, err := s.customerService.GetCustomersByCompanies(ctx, values)
		if err != nil {
			return nil, err
		}
		for _, customer := range customers {
			recipients = append(recipients, notificationRecipient{
				UserID:       customer.ID,
				Email:        customer.Email,
				DeviceTokens: customer.DeviceToken,
			})
		}
	} else if target == string(TargetAudienceSpecificCustomer) {
		customers, err := s.customerService.GetCustomersByIDs(ctx, values)
		if err != nil {
			return nil, err
		}
		for _, customer := range customers {
			recipients = append(recipients, notificationRecipient{
				UserID:       customer.ID,
				Email:        customer.Email,
				DeviceTokens: customer.DeviceToken,
			})
		}
	} else {
		offset := 0
		for {
			customers, err := s.customerService.SearchNotification(ctx, &customer.SearchCustomersForm{}, &response.Pagination{Limit: 100, Offset: offset})
			if err != nil {
				return nil, err
			}
			for _, customer := range customers {
				recipients = append(recipients, notificationRecipient{
					UserID:       customer.ID,
					Email:        customer.Email,
					DeviceTokens: customer.DeviceToken,
				})
			}
			offset += 100
			if len(customers) == 0 {
				break
			}
		}
	}

	if len(recipients) == 0 {
		return nil, errors.New("no customers found")
	}

	return recipients, nil
}
