package notification

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

	"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
	userService     user.Service
	customerService customer.Service
	logger          logger.Logger
}

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

// Send sends a multiple notification
func (s *notificationService) Send(ctx context.Context, req *NotificationRequest) error {
	s.logger.Info("Sending notification", map[string]interface{}{
		"recipient": req.Recipient,
		"type":      req.Type,
		"priority":  req.Priority,
	})

	if req.Target == TargetAudienceAllUsers || req.Target == TargetAudienceSpecificUsers {
		recipients, err := s.getUsers(ctx, req.Recipient)
		if err != nil {
			return err
		}
		for _, recipient := range recipients {
			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,
						Metadata: map[string]any{
							"tender": strings.TrimSpace(req.Tender),
						},
						Methods: notification.NotificationMethods{
							Push: v,
						},

						ScheduledAt: req.ScheduleAt,
					})
				}
			}

			if slices.Contains(req.Channels, DeliveryChannelEmail) {
				if 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,
						Metadata: map[string]any{
							"tender": strings.TrimSpace(req.Tender),
						},
						Methods: notification.NotificationMethods{
							Email: recipient.Email,
						},
						ScheduledAt: req.ScheduleAt,
					})
				}
			}
		}
	} else {
		recipients, err := s.getCustomers(ctx, req.Recipient, string(req.Target))
		if err != nil {
			return err
		}
		for _, recipient := range recipients {
			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,
						Methods: notification.NotificationMethods{
							Push: v,
						},
						ScheduledAt: req.ScheduleAt,
						Metadata: map[string]any{
							"tender": strings.TrimSpace(req.Tender),
						},
					})
				}
			}

			if slices.Contains(req.Channels, DeliveryChannelEmail) {
				if 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,
						Methods: notification.NotificationMethods{
							Email: recipient.Email,
						},
						ScheduledAt: req.ScheduleAt,
						Metadata: map[string]any{
							"tender": strings.TrimSpace(req.Tender),
						},
					})
				}
			}
		}
		s.logger.Info("Notification sent", map[string]interface{}{
			"recipient": req.Recipient,
			"channels":  req.Channels,
		})

		return nil
	}
	return nil
}

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,
	})

	notifications, err := s.sdk.GetNotifications(ctx, &notification.GetNotificationsRequest{
		Status:    req.Status,
		Method:    req.Method,
		EventType: req.EventType,
		Type:      req.Type,
		UserID:    req.Recipient,
		Seen:      req.Seen,
		Priority:  req.Priority,
		PerPage:   pagination.Limit,
		Page:      pagination.Offset / pagination.Limit,
	})
	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 notifications.Data {
		createdAt, _ := time.Parse(time.RFC3339, notification.CreatedAt)
		createdAtUnix := createdAt.Unix()
		updatedAt, _ := time.Parse(time.RFC3339, notification.UpdatedAt)
		updatedAtUnix := updatedAt.Unix()
		if createdAtUnix < 0 {
			createdAtUnix = 0
		}
		if updatedAtUnix < 0 {
			updatedAtUnix = 0
		}

		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
		}

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

	return &NotificationListResponse{
		Notifications: notificationsResponse,
		Meta: &response.Meta{
			Total:  notifications.Pagination.Total,
			Limit:  notifications.Pagination.PerPage,
			Offset: pagination.Offset,
			Page:   notifications.Pagination.CurrentPage,
			Pages:  notifications.Pagination.TotalPages,
		},
	}, 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.sdk.GetNotification(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.Data.Link != "" {
		linkPtr = &notification.Data.Link
	}

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

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

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

	createdAt := notification.Data.CreatedAt.Unix()
	updatedAt := notification.Data.UpdatedAt.Unix()
	if createdAt < 0 {
		createdAt = 0
	}
	if updatedAt < 0 {
		updatedAt = 0
	}

	detailedResponse := &NotificationResponse{
		ID:          notification.Data.ID,
		UserID:      notification.Data.UserID,
		Recipient:   recipient,
		Title:       notification.Data.Title,
		Message:     notification.Data.Message,
		Link:        linkPtr,
		Image:       imagePtr,
		Type:        notification.Data.Type,
		Priority:    notification.Data.Priority,
		EventType:   notification.Data.EventType,
		CreatedAt:   createdAt,
		UpdatedAt:   updatedAt,
		Methods:     notification.Data.Methods,
		Metadata:    notification.Data.Metadata,
		Status:      notification.Data.Status,
		Seen:        notification.Data.Seen,
		SeenAt:      notification.Data.SeenAt,
		ScheduledAt: notification.Data.ScheduledAt,
		IsScheduled: notification.Data.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,
	})

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

	if notif.Data.UserID != userID {
		return errors.New("notification not found")
	}

	_, err = s.sdk.MarkSeen(ctx, notificationID)
	if err != nil {
		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.sdk.AllMarkSeen(ctx, userID)
	if err != nil {
		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(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) {
		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
}
