package device

import (
	"context"

	"github.com/google/uuid"

	"nexavpn/backend/internal/gateway"
	"nexavpn/backend/internal/ipam"
	"nexavpn/backend/internal/policy"
	"nexavpn/backend/internal/profile"
)

type Service struct {
	repo           Repository
	policyService  *policy.Service
	gatewayService *gateway.Service
	ipamService    *ipam.Service
}

func NewService(repo Repository, policyService *policy.Service, gatewayService *gateway.Service, ipamService *ipam.Service) *Service {
	return &Service{
		repo:           repo,
		policyService:  policyService,
		gatewayService: gatewayService,
		ipamService:    ipamService,
	}
}

func (s *Service) Enroll(ctx context.Context, userID uuid.UUID, input EnrollRequest, privateKeyPlaceholder string) (EnrollmentResponse, error) {
	selectedGateway, err := s.gatewayService.SelectActive(ctx)
	if err != nil {
		return EnrollmentResponse{}, err
	}

	assignedIP, err := s.repo.FindNextAvailableIP(ctx, selectedGateway.ID, selectedGateway.VPNCIDR, 10)
	if err != nil {
		assignedIP, err = s.ipamService.Allocate(selectedGateway.VPNCIDR, 10)
		if err != nil {
			return EnrollmentResponse{}, err
		}
	}

	destinations, err := s.policyService.ResolveDestinations(ctx, userID, nil)
	if err != nil {
		return EnrollmentResponse{}, err
	}
	if len(destinations) == 0 {
		destinations = []string{"172.16.10.0/24"}
	}

	enrollment, err := s.repo.Enroll(ctx, userID, selectedGateway.ID, input, assignedIP, selectedGateway.DNSServers, destinations)
	if err != nil {
		return EnrollmentResponse{}, err
	}

	destinations, err = s.policyService.ResolveDestinations(ctx, userID, &enrollment.Device.ID)
	if err != nil {
		return EnrollmentResponse{}, err
	}
	if len(destinations) == 0 {
		destinations = []string{"172.16.10.0/24"}
	}

	enrollment.Peer = PeerView{
		AssignedIP: assignedIP,
		DNSServers: selectedGateway.DNSServers,
		AllowedIPs: destinations,
		Gateway: GatewayView{
			ID:        selectedGateway.ID,
			Name:      selectedGateway.Name,
			Endpoint:  selectedGateway.Endpoint,
			PublicKey: selectedGateway.PublicKey,
		},
		ProfileRevision: 1,
	}
	for _, destination := range destinations {
		enrollment.Resources = append(enrollment.Resources, Resource{
			Type:  "cidr",
			Value: destination,
			Label: destination,
		})
	}

	enrollment.Profile = ProfileView{
		Format: "wireguard",
		Content: profile.BuildWireGuardConfig(profile.BuildInput{
			PrivateKey:       privateKeyPlaceholder,
			Address:          assignedIP,
			DNSServers:       selectedGateway.DNSServers,
			ServerPublicKey:  selectedGateway.PublicKey,
			ServerEndpoint:   selectedGateway.Endpoint,
			AllowedIPs:       destinations,
			PersistentKeepal: 25,
		}),
	}

	return enrollment, nil
}

func (s *Service) ListByUser(ctx context.Context, userID uuid.UUID) ([]Device, error) {
	return s.repo.ListByUser(ctx, userID)
}

func (s *Service) ListAll(ctx context.Context) ([]Device, error) {
	return s.repo.ListAll(ctx)
}

func (s *Service) GetLatestEnrollmentByUser(ctx context.Context, userID uuid.UUID) (EnrollmentResponse, error) {
	enrollment, err := s.repo.GetLatestEnrollmentByUser(ctx, userID)
	if err != nil {
		return EnrollmentResponse{}, err
	}
	return withDebugProfile(enrollment), nil
}

func (s *Service) GetEnrollmentByDeviceID(ctx context.Context, deviceID uuid.UUID) (EnrollmentResponse, error) {
	enrollment, err := s.repo.GetEnrollmentByDeviceID(ctx, deviceID)
	if err != nil {
		return EnrollmentResponse{}, err
	}
	return withDebugProfile(enrollment), nil
}

func (s *Service) GetConnectionStatus(ctx context.Context, userID uuid.UUID) (ConnectionStatus, error) {
	enrollment, err := s.repo.GetLatestEnrollmentByUser(ctx, userID)
	if err != nil {
		return ConnectionStatus{
			Status:    "disconnected",
			Resources: []Resource{},
		}, nil
	}

	lastSync := "just now"
	return ConnectionStatus{
		Status:       "provisioned",
		AssignedIP:   enrollment.Peer.AssignedIP,
		LastSyncTime: &lastSync,
		Resources:    enrollment.Resources,
	}, nil
}

func (s *Service) Revoke(ctx context.Context, deviceID uuid.UUID) error {
	return s.repo.Revoke(ctx, deviceID)
}

func (s *Service) Rotate(ctx context.Context, deviceID uuid.UUID) error {
	return s.repo.Rotate(ctx, deviceID)
}

func withDebugProfile(enrollment EnrollmentResponse) EnrollmentResponse {
	enrollment.Profile = ProfileView{
		Format: "wireguard",
		Content: profile.BuildWireGuardConfig(profile.BuildInput{
			PrivateKey:       "__CLIENT_PRIVATE_KEY_REQUIRED__",
			Address:          enrollment.Peer.AssignedIP,
			DNSServers:       enrollment.Peer.DNSServers,
			ServerPublicKey:  enrollment.Peer.Gateway.PublicKey,
			ServerEndpoint:   enrollment.Peer.Gateway.Endpoint,
			AllowedIPs:       enrollment.Peer.AllowedIPs,
			PersistentKeepal: 25,
		}),
	}
	return enrollment
}
