package mail

import (
	"context"
	"errors"

	"github.com/jackc/pgx/v5"

	"github.com/ultisuite/ulti-backend/internal/filescan"
	"github.com/ultisuite/ulti-backend/internal/nextcloud"
)

const driveSourceUltimail = "ultimail"

func (s *Service) SetDriveUploader(uploader DriveUploader) {
	s.driveUploader = uploader
}

func (s *Service) SetFileScanner(scanner *filescan.Scanner) {
	s.scanner = scanner
}

func (s *Service) SaveAttachmentToDrive(
	ctx context.Context,
	externalID, email, sub, displayName, messageID, attachmentID, folderPath string,
) (string, error) {
	if s.driveUploader == nil {
		return "", ErrDriveUnavailable
	}
	if s.storage == nil {
		return "", errors.New("object storage unavailable")
	}

	folderPath = normalizeDriveFolder(folderPath)

	userID, err := s.ensureMessageOwned(ctx, externalID, messageID)
	if err != nil {
		return "", err
	}

	var (
		filename    string
		contentType string
		size        int64
		s3Key       string
		existing    string
	)
	err = s.db.QueryRow(ctx, `
		SELECT a.filename, a.content_type, a.size, a.s3_key, COALESCE(a.drive_path, '')
		FROM attachments a
		WHERE a.id = $1 AND a.message_id = $2
	`, attachmentID, messageID).Scan(&filename, &contentType, &size, &s3Key, &existing)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return "", ErrAttachmentNotFound
		}
		return "", err
	}
	if existing != "" {
		return existing, nil
	}

	if folderPath != "/" {
		if err := s.driveUploader.EnsureNextcloudFolder(ctx, email, sub, displayName, folderPath); err != nil {
			return "", err
		}
	}

	destPath, err := uniqueDriveFilePath(ctx, s.driveUploader, email, sub, displayName, folderPath, filename)
	if err != nil {
		return "", err
	}

	obj, err := s.storage.Get(ctx, s3Key)
	if err != nil {
		return "", err
	}
	defer obj.Close()

	destPath = nextcloud.NormalizeClientPath(destPath)
	if err := s.driveUploader.UploadFile(ctx, email, sub, displayName, destPath, obj, contentType, size); err != nil {
		return "", err
	}
	s.driveUploader.NotifyFileChanged(externalID, destPath)

	if err := s.recordDriveFileSource(ctx, userID, destPath, driveSourceUltimail); err != nil {
		return "", err
	}

	_, err = s.db.Exec(ctx, `
		UPDATE attachments SET drive_path = $1 WHERE id = $2 AND message_id = $3
	`, destPath, attachmentID, messageID)
	if err != nil {
		return "", err
	}
	return destPath, nil
}

func (s *Service) SaveMessageAttachmentsToDrive(
	ctx context.Context,
	externalID, email, sub, displayName, messageID, folderPath string,
) ([]map[string]any, error) {
	list, err := s.ListMessageAttachments(ctx, externalID, messageID)
	if err != nil {
		return nil, err
	}
	out := make([]map[string]any, 0, len(list))
	for _, att := range list {
		if att["is_inline"] == true {
			continue
		}
		id, _ := att["id"].(string)
		if id == "" {
			continue
		}
		drivePath, err := s.SaveAttachmentToDrive(ctx, externalID, email, sub, displayName, messageID, id, folderPath)
		if err != nil {
			return nil, err
		}
		entry := make(map[string]any, len(att)+1)
		for k, v := range att {
			entry[k] = v
		}
		entry["drive_path"] = drivePath
		out = append(out, entry)
	}
	return out, nil
}

func (s *Service) recordDriveFileSource(ctx context.Context, userID, filePath, source string) error {
	filePath = nextcloud.NormalizeClientPath(filePath)
	_, err := s.db.Exec(ctx, `
		INSERT INTO drive_file_sources (user_id, file_path, source)
		VALUES ($1, $2, $3)
		ON CONFLICT (user_id, file_path) DO UPDATE SET source = EXCLUDED.source
	`, userID, filePath, source)
	return err
}
