package service

import (
	"context"
	"fmt"
	"net/url"

	"eslogad-be/internal/config"
	"eslogad-be/internal/contract"

	"github.com/google/uuid"
	novu "github.com/novuhq/go-novu/lib"
)

type NotificationService interface {
	TriggerNotification(ctx context.Context, req *contract.TriggerNotificationRequest) (*contract.TriggerNotificationResponse, error)
	BulkTriggerNotification(ctx context.Context, req *contract.BulkTriggerNotificationRequest) (*contract.BulkTriggerNotificationResponse, error)
	GetSubscriber(ctx context.Context, userID uuid.UUID) (*contract.GetSubscriberResponse, error)
	UpdateSubscriberChannel(ctx context.Context, req *contract.UpdateSubscriberChannelRequest) (*contract.UpdateSubscriberChannelResponse, error)
}

type NotificationServiceImpl struct {
	client        *novu.APIClient
	config        *config.NovuConfig
	userProcessor UserProcessorForNotification
}

type UserProcessorForNotification interface {
	GetUserByID(ctx context.Context, id uuid.UUID) (*contract.UserResponse, error)
}

func NewNotificationService(cfg *config.NovuConfig, userProcessor UserProcessorForNotification) *NotificationServiceImpl {
	var client *novu.APIClient
	if cfg.APIKey != "" {
		// Create Novu config with backend URL
		novuConfig := &novu.Config{}
		if cfg.BaseURL != "" {
			backendURL, err := url.Parse(cfg.BaseURL)
			if err == nil {
				novuConfig.BackendURL = backendURL
			}
		}
		client = novu.NewAPIClient(cfg.APIKey, novuConfig)
	}

	return &NotificationServiceImpl{
		client:        client,
		config:        cfg,
		userProcessor: userProcessor,
	}
}

func (s *NotificationServiceImpl) TriggerNotification(ctx context.Context, req *contract.TriggerNotificationRequest) (*contract.TriggerNotificationResponse, error) {
	if s.client == nil {
		return &contract.TriggerNotificationResponse{
			Success: false,
			Message: "notification service not configured",
		}, nil
	}

	subscriberID := req.UserID.String()
	
	_, err := s.ensureSubscriberExists(ctx, req.UserID)
	if err != nil {
		return &contract.TriggerNotificationResponse{
			Success: false,
			Message: fmt.Sprintf("failed to ensure subscriber exists: %v", err),
		}, nil
	}

	// Prepare the trigger payload
	to := map[string]interface{}{
		"subscriberId": subscriberID,
	}

	// Add additional recipient information if provided
	if req.To != nil {
		if req.To.Email != "" {
			to["email"] = req.To.Email
		}
		if req.To.Phone != "" {
			to["phone"] = req.To.Phone
		}
	}

	// Prepare overrides
	overrides := make(map[string]interface{})
	if req.Overrides != nil {
		if req.Overrides.Email != nil {
			overrides["email"] = req.Overrides.Email
		}
		if req.Overrides.SMS != nil {
			overrides["sms"] = req.Overrides.SMS
		}
		if req.Overrides.InApp != nil {
			overrides["in_app"] = req.Overrides.InApp
		}
		if req.Overrides.Push != nil {
			overrides["push"] = req.Overrides.Push
		}
		if req.Overrides.Chat != nil {
			overrides["chat"] = req.Overrides.Chat
		}
	}

	// Trigger the notification using the template ID
	triggerPayload := novu.ITriggerPayloadOptions{
		To:        to,
		Payload:   req.TemplateData,
		Overrides: overrides,
	}

	resp, err := s.client.EventApi.Trigger(ctx, req.TemplateID, triggerPayload)
	if err != nil {
		return &contract.TriggerNotificationResponse{
			Success: false,
			Message: fmt.Sprintf("failed to trigger notification: %v", err),
		}, nil
	}

	// Extract transaction ID from response
	transactionID := ""
	if respData, ok := resp.Data.(map[string]interface{}); ok {
		if txID, exists := respData["transactionId"]; exists {
			transactionID = fmt.Sprintf("%v", txID)
		}
	}

	return &contract.TriggerNotificationResponse{
		Success:       true,
		TransactionID: transactionID,
		Message:       "notification triggered successfully",
	}, nil
}

func (s *NotificationServiceImpl) BulkTriggerNotification(ctx context.Context, req *contract.BulkTriggerNotificationRequest) (*contract.BulkTriggerNotificationResponse, error) {
	if s.client == nil {
		return &contract.BulkTriggerNotificationResponse{
			Success:     false,
			TotalSent:   0,
			TotalFailed: len(req.UserIDs),
		}, nil
	}

	results := make([]contract.NotificationResult, 0, len(req.UserIDs))
	successCount := 0
	failedCount := 0

	for _, userID := range req.UserIDs {
		// Create individual trigger request
		triggerReq := &contract.TriggerNotificationRequest{
			UserID:       userID,
			TemplateID:   req.TemplateID,
			TemplateData: req.TemplateData,
			Overrides:    req.Overrides,
		}

		resp, err := s.TriggerNotification(ctx, triggerReq)

		result := contract.NotificationResult{
			UserID:  userID,
			Success: resp.Success,
		}

		if resp.Success {
			result.TransactionID = resp.TransactionID
			successCount++
		} else {
			if err != nil {
				result.Error = err.Error()
			} else {
				result.Error = resp.Message
			}
			failedCount++
		}

		results = append(results, result)
	}

	return &contract.BulkTriggerNotificationResponse{
		Success:     failedCount == 0,
		TotalSent:   successCount,
		TotalFailed: failedCount,
		Results:     results,
	}, nil
}

func (s *NotificationServiceImpl) GetSubscriber(ctx context.Context, userID uuid.UUID) (*contract.GetSubscriberResponse, error) {
	if s.client == nil {
		return nil, fmt.Errorf("notification service not configured")
	}

	subscriberID := userID.String()

	// Try to get the subscriber
	subscriber, err := s.client.SubscriberApi.Get(ctx, subscriberID)
	if err != nil {
		// If subscriber doesn't exist, create it
		_, createErr := s.ensureSubscriberExists(ctx, userID)
		if createErr != nil {
			return nil, fmt.Errorf("failed to get or create subscriber: %w", createErr)
		}

		// Try to get again after creation
		subscriber, err = s.client.SubscriberApi.Get(ctx, subscriberID)
		if err != nil {
			return nil, fmt.Errorf("failed to get subscriber after creation: %w", err)
		}
	}

	// Convert Novu subscriber to our response format
	response := &contract.GetSubscriberResponse{
		SubscriberID: subscriberID,
	}

	if subData, ok := subscriber.Data.(map[string]interface{}); ok {
		if email, exists := subData["email"]; exists {
			response.Email = fmt.Sprintf("%v", email)
		}
		if firstName, exists := subData["firstName"]; exists {
			response.FirstName = fmt.Sprintf("%v", firstName)
		}
		if lastName, exists := subData["lastName"]; exists {
			response.LastName = fmt.Sprintf("%v", lastName)
		}
		if phone, exists := subData["phone"]; exists {
			response.Phone = fmt.Sprintf("%v", phone)
		}
		if avatar, exists := subData["avatar"]; exists {
			response.Avatar = fmt.Sprintf("%v", avatar)
		}
		if data, exists := subData["data"]; exists {
			if dataMap, ok := data.(map[string]interface{}); ok {
				response.Data = dataMap
			}
		}
		if channels, exists := subData["channels"]; exists {
			if channelList, ok := channels.([]interface{}); ok {
				response.Channels = make([]contract.ChannelCredentials, 0, len(channelList))
				for _, ch := range channelList {
					if chMap, ok := ch.(map[string]interface{}); ok {
						channelCred := contract.ChannelCredentials{}
						if chType, exists := chMap["providerId"]; exists {
							channelCred.Channel = fmt.Sprintf("%v", chType)
						}
						if creds, exists := chMap["credentials"]; exists {
							if credMap, ok := creds.(map[string]interface{}); ok {
								channelCred.Credentials = credMap
							}
						}
						response.Channels = append(response.Channels, channelCred)
					}
				}
			}
		}
	}

	return response, nil
}

func (s *NotificationServiceImpl) UpdateSubscriberChannel(ctx context.Context, req *contract.UpdateSubscriberChannelRequest) (*contract.UpdateSubscriberChannelResponse, error) {
	if s.client == nil {
		return &contract.UpdateSubscriberChannelResponse{
			Success: false,
			Message: "notification service not configured",
		}, nil
	}

	subscriberID := req.UserID.String()

	// Ensure subscriber exists
	_, err := s.ensureSubscriberExists(ctx, req.UserID)
	if err != nil {
		return &contract.UpdateSubscriberChannelResponse{
			Success: false,
			Message: fmt.Sprintf("failed to ensure subscriber exists: %v", err),
		}, nil
	}

	// Since the Novu Go SDK doesn't have UpdateCredentials, we'll update the subscriber data instead
	// Get user info to update subscriber
	user, err := s.userProcessor.GetUserByID(ctx, req.UserID)
	if err != nil {
		return &contract.UpdateSubscriberChannelResponse{
			Success: false,
			Message: fmt.Sprintf("failed to get user data: %v", err),
		}, nil
	}

	// Prepare subscriber data with channel credentials stored in data
	data := map[string]interface{}{
		"userId":    user.ID.String(),
		"email":     user.Email,
		"isActive":  user.IsActive,
		"updatedAt": user.UpdatedAt,
	}

	// Store channel credentials in the subscriber data
	channelKey := fmt.Sprintf("channel_%s", req.Channel)
	data[channelKey] = req.Credentials

	// Update subscriber with new data
	updateData := novu.SubscriberPayload{
		Email:     user.Email,
		FirstName: user.Name,
		LastName:  "",
		Phone:     "",
		Avatar:    "",
		Data:      data,
	}

	_, err = s.client.SubscriberApi.Update(ctx, subscriberID, updateData)
	if err != nil {
		return &contract.UpdateSubscriberChannelResponse{
			Success: false,
			Message: fmt.Sprintf("failed to update subscriber channel: %v", err),
		}, nil
	}

	return &contract.UpdateSubscriberChannelResponse{
		Success: true,
		Message: "subscriber channel updated successfully",
	}, nil
}

func (s *NotificationServiceImpl) ensureSubscriberExists(ctx context.Context, userID uuid.UUID) (bool, error) {
	subscriberID := userID.String()

	_, err := s.client.SubscriberApi.Get(ctx, subscriberID)
	if err == nil {
		return false, nil
	}

	user, err := s.userProcessor.GetUserByID(ctx, userID)
	if err != nil {
		return false, fmt.Errorf("failed to get user data: %w", err)
	}

	data := map[string]interface{}{
		"userId":    user.ID.String(),
		"email":     user.Email,
		"isActive":  user.IsActive,
		"createdAt": user.CreatedAt,
	}

	if user.Roles != nil && len(user.Roles) > 0 {
		roles := make([]map[string]interface{}, len(user.Roles))
		for i, role := range user.Roles {
			roles[i] = map[string]interface{}{
				"id":   role.ID.String(),
				"name": role.Name,
				"code": role.Code,
			}
		}
		data["roles"] = roles
	}

	if user.DepartmentResponse != nil && len(user.DepartmentResponse) > 0 {
		depts := make([]map[string]interface{}, len(user.DepartmentResponse))
		for i, dept := range user.DepartmentResponse {
			depts[i] = map[string]interface{}{
				"id":   dept.ID.String(),
				"name": dept.Name,
				"code": dept.Code,
			}
		}
		data["departments"] = depts
	}

	subscriber := novu.SubscriberPayload{
		Email:     user.Email,
		FirstName: user.Name,
		LastName:  "",
		Phone:     "",
		Avatar:    "",
		Data:      data,
	}

	_, err = s.client.SubscriberApi.Identify(ctx, subscriberID, subscriber)
	if err != nil {
		return false, fmt.Errorf("failed to create subscriber: %w", err)
	}

	return true, nil
}
