package service

import (
	"context"
	"errors"
	"fmt"

	vulnmodel "security-center-backend/internal/modules/vuln/model"
)

var ErrInvalidAttachmentReference = errors.New("invalid attachment reference")

func (s *Service) CreatePendingAttachment(ctx context.Context, uploaderID uint64, bucket string, key string, requestedContentType string) (*vulnmodel.VulnerabilityAttachment, error) {
	if s.attachRepo == nil {
		return nil, fmt.Errorf("attachment repository not configured")
	}
	attachment := &vulnmodel.VulnerabilityAttachment{
		UploaderID:  uploaderID,
		S3Bucket:    bucket,
		S3Key:       key,
		ContentType: requestedContentType,
		Status:      vulnmodel.AttachmentStatusPending,
		SizeBytes:   0,
	}
	if err := s.attachRepo.Create(ctx, attachment); err != nil {
		return nil, err
	}
	return attachment, nil
}

func (s *Service) GetAttachment(ctx context.Context, id uint64) (*vulnmodel.VulnerabilityAttachment, error) {
	if s.attachRepo == nil {
		return nil, fmt.Errorf("attachment repository not configured")
	}
	return s.attachRepo.FindByID(ctx, id)
}

func (s *Service) ConfirmAttachment(ctx context.Context, uploaderID uint64, id uint64, contentType string, sizeBytes int64) (*vulnmodel.VulnerabilityAttachment, error) {
	if s.attachRepo == nil {
		return nil, fmt.Errorf("attachment repository not configured")
	}
	item, err := s.attachRepo.FindByID(ctx, id)
	if err != nil {
		return nil, err
	}
	if item.UploaderID != uploaderID {
		return nil, fmt.Errorf("forbidden")
	}
	if item.Status == vulnmodel.AttachmentStatusRejected {
		return nil, fmt.Errorf("rejected")
	}
	if err := s.attachRepo.UpdateFields(ctx, id, map[string]interface{}{
		"content_type": contentType,
		"size_bytes":   sizeBytes,
		"status":       vulnmodel.AttachmentStatusReady,
	}); err != nil {
		return nil, err
	}
	return s.attachRepo.FindByID(ctx, id)
}

func (s *Service) RejectAttachment(ctx context.Context, uploaderID uint64, id uint64) error {
	if s.attachRepo == nil {
		return fmt.Errorf("attachment repository not configured")
	}
	item, err := s.attachRepo.FindByID(ctx, id)
	if err != nil {
		return err
	}
	if item.UploaderID != uploaderID {
		return fmt.Errorf("forbidden")
	}
	return s.attachRepo.UpdateFields(ctx, id, map[string]interface{}{
		"status": vulnmodel.AttachmentStatusRejected,
	})
}

func (s *Service) AttachConfirmedAttachmentsToVulnerability(ctx context.Context, uploaderID uint64, vulnID uint64, attachmentIDs []uint64) error {
	if s.attachRepo == nil {
		return fmt.Errorf("attachment repository not configured")
	}
	return s.attachRepo.AttachToVulnerability(ctx, uploaderID, vulnID, attachmentIDs)
}

func (s *Service) ValidateDescriptionAttachments(ctx context.Context, uploaderID uint64, vulnID uint64, attachmentIDs []uint64) error {
	if len(attachmentIDs) == 0 {
		return nil
	}
	if s.attachRepo == nil {
		return fmt.Errorf("attachment repository not configured")
	}
	return s.validateDescriptionAttachments(ctx, uploaderID, vulnID, attachmentIDs, false)
}

func (s *Service) ValidateAndAttachDescriptionAttachments(ctx context.Context, uploaderID uint64, vulnID uint64, attachmentIDs []uint64) error {
	if len(attachmentIDs) == 0 {
		return nil
	}
	if s.attachRepo == nil {
		return fmt.Errorf("attachment repository not configured")
	}
	ids := uniqueAttachmentIDs(attachmentIDs)
	if err := s.validateDescriptionAttachments(ctx, uploaderID, vulnID, ids, false); err != nil {
		return err
	}
	if err := s.attachRepo.AttachToVulnerability(ctx, uploaderID, vulnID, ids); err != nil {
		return err
	}
	return s.validateDescriptionAttachments(ctx, uploaderID, vulnID, ids, true)
}

func (s *Service) validateDescriptionAttachments(ctx context.Context, uploaderID uint64, vulnID uint64, attachmentIDs []uint64, requireBoundToVulnerability bool) error {
	for _, attachmentID := range uniqueAttachmentIDs(attachmentIDs) {
		item, err := s.attachRepo.FindByID(ctx, attachmentID)
		if err != nil {
			return fmt.Errorf("%w", ErrInvalidAttachmentReference)
		}
		if item.Status != vulnmodel.AttachmentStatusReady {
			return fmt.Errorf("%w", ErrInvalidAttachmentReference)
		}

		boundVulnID := uint64(0)
		if item.VulnerabilityID != nil {
			boundVulnID = *item.VulnerabilityID
		}
		if boundVulnID != 0 {
			if boundVulnID == vulnID && vulnID != 0 {
				continue
			}
			return fmt.Errorf("%w", ErrInvalidAttachmentReference)
		}
		if item.UploaderID != uploaderID {
			return fmt.Errorf("%w", ErrInvalidAttachmentReference)
		}
		if requireBoundToVulnerability {
			return fmt.Errorf("%w", ErrInvalidAttachmentReference)
		}
	}
	return nil
}

func uniqueAttachmentIDs(ids []uint64) []uint64 {
	if len(ids) == 0 {
		return nil
	}
	result := make([]uint64, 0, len(ids))
	seen := make(map[uint64]struct{}, len(ids))
	for _, id := range ids {
		if id == 0 {
			continue
		}
		if _, ok := seen[id]; ok {
			continue
		}
		seen[id] = struct{}{}
		result = append(result, id)
	}
	return result
}
