package file_service

import (
	"context"
	"errors"
	"strings"
	"testing"

	"evening_detective_server/internal/modules/file_storage"
	"evening_detective_server/internal/modules/string_tools"
)

// fakeStorage — in-memory реализация IFileStorage для тестов.
type fakeStorage struct {
	files map[string]*file_storage.File
}

func newFakeStorage() *fakeStorage {
	return &fakeStorage{files: map[string]*file_storage.File{}}
}

func (s *fakeStorage) Put(_ context.Context, f *file_storage.File) error {
	cp := *f
	cp.Data = append([]byte(nil), f.Data...)
	s.files[f.Name] = &cp
	return nil
}

func (s *fakeStorage) Get(_ context.Context, name string) (*file_storage.File, error) {
	f, ok := s.files[name]
	if !ok {
		return nil, file_storage.ErrFileNotFound
	}
	return f, nil
}

func (s *fakeStorage) Delete(_ context.Context, name string) error {
	delete(s.files, name)
	return nil
}

func (s *fakeStorage) MimeType(filename string) string {
	return file_storage.FileType(filename)
}

func newService(maxFileSize int) *FileService {
	return NewFileService(newFakeStorage(), string_tools.NewStringTools(), maxFileSize)
}

func mustUpload(t *testing.T, svc *FileService, filename string, data []byte) (string, string) {
	t.Helper()
	name, fileType, err := svc.UploadFile(context.Background(), filename, data)
	if err != nil {
		t.Fatalf("UploadFile(%q): %v", filename, err)
	}
	return name, fileType
}

func TestUploadFileValid(t *testing.T) {
	svc := newService(1 << 20)

	cases := []struct {
		filename string
		data     []byte
		wantType string
		wantExt  string
		wantMime string
	}{
		{"улика.pdf", []byte("%PDF-1.7 content"), "pdf", ".pdf", "application/pdf"},
		{"photo.png", []byte("\x89PNG\r\n\x1a\ncontent"), "image", ".png", "image/png"},
		{"photo.PNG", []byte("\x89PNG\r\n\x1a\ncontent"), "image", ".png", "image/png"},
		{"photo.jpg", []byte("\xff\xd8\xff\xe0jpeg-content"), "image", ".jpg", "image/jpeg"},
		{"audio.mp3", []byte("ID3\x04\x00\x00\x00\x00\x00\x00audio"), "audio", ".mp3", "audio/mpeg"},
		{"audio.ogg", []byte("OggS\x00\x02audio"), "audio", ".ogg", "application/ogg"},
		{"audio.wav", []byte("RIFF\x24\x00\x00\x00WAVEfmt "), "audio", ".wav", "audio/wave"},
	}
	for _, c := range cases {
		name, fileType := mustUpload(t, svc, c.filename, c.data)
		if fileType != c.wantType {
			t.Errorf("%s: fileType = %q, want %q", c.filename, fileType, c.wantType)
		}
		if !strings.HasSuffix(name, c.wantExt) {
			t.Errorf("%s: имя %q не оканчивается на %q", c.filename, name, c.wantExt)
		}
		if !strings.Contains(name, "_") {
			t.Errorf("%s: имя %q не содержит уникальный hex-суффикс", c.filename, name)
		}
		// Сохранённый MIME соответствует содержимому (sniffed-тип).
		stored, err := svc.fileStorage.Get(context.Background(), name)
		if err != nil {
			t.Fatalf("Get(%q): %v", name, err)
		}
		if stored.Mime != c.wantMime {
			t.Errorf("%s: MIME = %q, want %q", c.filename, stored.Mime, c.wantMime)
		}
	}
}

func TestUploadFileUniqueNames(t *testing.T) {
	svc := newService(1 << 20)
	data := []byte("%PDF-1.7 same content")

	first, _ := mustUpload(t, svc, "clue.pdf", data)
	second, _ := mustUpload(t, svc, "clue.pdf", data)

	if first == second {
		t.Errorf("одинаковые имена %q — коллизия в бакете", first)
	}
}

func TestUploadFileValidation(t *testing.T) {
	svc := newService(1 << 20)
	pdf := []byte("%PDF-1.7 content")

	cases := []struct {
		name     string
		filename string
		data     []byte
		wantErr  error
	}{
		{"пустое имя", "", pdf, ErrEmptyName},
		{"пустые данные", "clue.pdf", nil, ErrEmptyData},
		{"нет расширения", "clue", pdf, ErrInvalidExtension},
		{"недопустимое расширение", "clue.exe", []byte("MZ\x90\x00"), ErrInvalidExtension},
		{"пустой base (.pdf)", ".pdf", pdf, ErrEmptyName},
		{"base из не-латиницы (!!!.pdf)", "!!!.pdf", pdf, ErrEmptyName},
		{"html под видом png", "photo.png", []byte("<html>hi</html>"), ErrInvalidContentType},
		{"octet-stream", "clue.pdf", []byte{0x00, 0x01, 0x02, 0x03}, ErrInvalidContentType},
		{"mismatch pdf+png", "clue.pdf", []byte("\x89PNG\r\n\x1a\ncontent"), ErrInvalidContentType},
	}
	for _, c := range cases {
		_, _, err := svc.UploadFile(context.Background(), c.filename, c.data)
		if !errors.Is(err, c.wantErr) {
			t.Errorf("%s: err = %v, want %v", c.name, err, c.wantErr)
		}
	}
}

func TestUploadFileTooLarge(t *testing.T) {
	svc := newService(1024)

	// ровно лимит — проходит
	if _, _, err := svc.UploadFile(context.Background(), "clue.pdf", bytes1024()); err != nil {
		t.Errorf("ровно лимит: err = %v, want nil", err)
	}
	// больше лимита — ErrFileTooLarge
	_, _, err := svc.UploadFile(context.Background(), "clue.pdf", bytes1025())
	if !errors.Is(err, ErrFileTooLarge) {
		t.Errorf("больше лимита: err = %v, want ErrFileTooLarge", err)
	}
}

func bytes1024() []byte {
	b := make([]byte, 1024)
	copy(b, "%PDF-1.7 ")
	for i := 8; i < len(b); i++ {
		b[i] = 'x'
	}
	return b
}

func bytes1025() []byte {
	return append(bytes1024(), 'y')
}
