// Copyright 2025 AGNTCY Contributors (https://github.com/agntcy)
// SPDX-License-Deviceentifier: Apache-2.0

package bff

import (
	"context"
	"errors"
	"fmt"
	"time"

	devicecore "github.com/agntcy/identity-service/internal/core/device"
	devicetypes "github.com/agntcy/identity-service/internal/core/device/types"
	identitycontext "github.com/agntcy/identity-service/internal/pkg/context"
	"github.com/agntcy/identity-service/internal/pkg/errutil"
	"github.com/agntcy/identity-service/internal/pkg/pagination"
	"github.com/google/uuid"
)

type DeviceService interface {
	AddDevice(ctx context.Context, device *devicetypes.Device) (*devicetypes.Device, error)
	RegisterDevice(ctx context.Context, deviceID string, device *devicetypes.Device) error
	ListRegisteredDevices(
		ctx context.Context,
		paginationFilter pagination.PaginationFilter,
		query *string,
	) (*pagination.Pageable[devicetypes.Device], error)
	DeleteDevice(ctx context.Context, deviceID string) error
	TestDevice(ctx context.Context, deviceID string) error
}

var (
	errDeviceNotFound = errutil.NotFound("device.notFound", "Device not found.")
	errInvalidID      = errutil.ValidationFailed("device.idInvalid", "Invalid device ID.")
)

type deviceService struct {
	deviceRepository    devicecore.Repository
	notificationService NotificationService
}

func NewDeviceService(
	deviceRepository devicecore.Repository,
	notificationService NotificationService,
) DeviceService {
	return &deviceService{
		deviceRepository:    deviceRepository,
		notificationService: notificationService,
	}
}

func (s *deviceService) AddDevice(
	ctx context.Context,
	device *devicetypes.Device,
) (*devicetypes.Device, error) {
	if device == nil {
		return nil, errutil.ValidationFailed("device.invalidDevice", "Invalid device payload.")
	}

	userID, _ := identitycontext.GetUserID(ctx)

	device.ID = uuid.NewString()
	device.UserID = userID
	device.CreatedAt = time.Now().UTC()

	// Add the device to the repository.
	storedDevice, err := s.deviceRepository.AddDevice(ctx, device)
	if err != nil {
		return nil, fmt.Errorf("repository in AddDevice failed to add device: %w", err)
	}

	return storedDevice, nil
}

func (s *deviceService) RegisterDevice(
	ctx context.Context,
	deviceID string,
	device *devicetypes.Device,
) error {
	if device == nil {
		return errutil.ValidationFailed("device.invalidDevice", "Invalid device payload.")
	}

	if deviceID == "" {
		return errInvalidID
	}

	// Get the device from the repository.
	existingDevice, err := s.deviceRepository.GetDevice(ctx, deviceID)
	if err != nil {
		if errors.Is(err, devicecore.ErrDeviceNotFound) {
			return errDeviceNotFound
		}

		return fmt.Errorf("repository in RegisterDevice failed to get device %s: %w", deviceID, err)
	}

	// Update
	existingDevice.SubscriptionToken = device.SubscriptionToken
	existingDevice.Name = device.Name

	// Try to send a notification about the device registration.
	if err := s.notificationService.SendDeviceRegisteredNotification(existingDevice); err != nil {
		return fmt.Errorf(
			"failed to send notification for device registration (deviceID = %s): %w",
			existingDevice.ID,
			err,
		)
	}

	_, err = s.deviceRepository.UpdateDevice(ctx, existingDevice)
	if err != nil {
		return fmt.Errorf("repository in RegisterDevice failed to update device %s: %w", existingDevice.ID, err)
	}

	return nil
}

func (s *deviceService) ListRegisteredDevices(
	ctx context.Context,
	paginationFilter pagination.PaginationFilter,
	query *string,
) (*pagination.Pageable[devicetypes.Device], error) {
	page, err := s.deviceRepository.ListRegisteredDevices(ctx, paginationFilter, query)
	if err != nil {
		return nil, fmt.Errorf("repository in ListRegisteredDevices failed to fetch registered devices: %w", err)
	}

	return page, nil
}

func (s *deviceService) DeleteDevice(ctx context.Context, deviceID string) error {
	if deviceID == "" {
		return errInvalidID
	}

	device, err := s.deviceRepository.GetDevice(ctx, deviceID)
	if err != nil {
		if errors.Is(err, devicecore.ErrDeviceNotFound) {
			return errDeviceNotFound
		}

		return fmt.Errorf("repository in DeleteDevice failed to get device %s: %w", deviceID, err)
	}

	err = s.deviceRepository.DeleteDevice(ctx, device)
	if err != nil {
		return fmt.Errorf("repository in DeleteDevice failed to delete device %s: %w", deviceID, err)
	}

	return nil
}

func (s *deviceService) TestDevice(ctx context.Context, deviceID string) error {
	device, err := s.deviceRepository.GetDevice(ctx, deviceID)
	if err != nil {
		if errors.Is(err, devicecore.ErrDeviceNotFound) {
			return errDeviceNotFound
		}

		return fmt.Errorf("repository in TestDevice failed to get device %s: %w", deviceID, err)
	}

	err = s.notificationService.SendInfoNotification(device, "This is a test notification!")
	if err != nil {
		return fmt.Errorf("failed to send test notification: %w", err)
	}

	return nil
}
