package services

import (
	"archive/tar"
	"archive/zip"
	"bytes"
	"compress/gzip"
	"encoding/xml"
	"fmt"
	"image"
	"image/color"
	"image/png"
	"os"
	"path/filepath"
	"testing"
	"time"
)

// TestExtractZipMetadata tests .cbz (ZIP) metadata extraction
func TestExtractZipMetadata(t *testing.T) {
	tmpDir := t.TempDir()
	cbzFile := filepath.Join(tmpDir, "test.cbz")

	// Create a test ZIP archive with ComicInfo.xml and cover image
	if err := createTestCBZ(cbzFile); err != nil {
		t.Fatalf("Failed to create test CBZ: %v", err)
	}

	// Test metadata extraction
	comicInfo, coverImage, err := extractZipMetadata(cbzFile)
	if err != nil {
		t.Fatalf("extractZipMetadata failed: %v", err)
	}

	// Verify metadata
	if comicInfo == nil {
		t.Fatal("comicInfo is nil")
	}

	if comicInfo.Title != "Test Comic Title" {
		t.Errorf("Expected title 'Test Comic Title', got '%s'", comicInfo.Title)
	}

	if comicInfo.Series != "Test Series" {
		t.Errorf("Expected series 'Test Series', got '%s'", comicInfo.Series)
	}

	if comicInfo.Number != 1 {
		t.Errorf("Expected issue number 1, got %d", comicInfo.Number)
	}

	if comicInfo.Publisher != "Test Publisher" {
		t.Errorf("Expected publisher 'Test Publisher', got '%s'", comicInfo.Publisher)
	}

	if comicInfo.Writer != "Test Writer" {
		t.Errorf("Expected writer 'Test Writer', got '%s'", comicInfo.Writer)
	}

	// Verify cover image
	if len(coverImage) == 0 {
		t.Error("Cover image not extracted")
		// Debug: Check if the file was created properly
		t.Logf("Debug: Checking created CBZ file")
		r, err := zip.OpenReader(cbzFile)
		if err != nil {
			t.Logf("Failed to open CBZ for debug: %v", err)
			return
		}
		defer r.Close()
		for _, f := range r.File {
			t.Logf("File in CBZ: %s (isImage: %v)", f.Name, isImageFile(f.Name))
		}
	} else {
		// Verify it's a valid image
		_, format, err := image.Decode(bytes.NewReader(coverImage))
		if err != nil {
			t.Errorf("Extracted cover is not a valid image: %v", err)
		}
		t.Logf("Cover image format: %s, size: %d bytes", format, len(coverImage))
	}

	t.Logf("ZIP metadata extraction test passed: title=%s, series=%s, issue=%d",
		comicInfo.Title, comicInfo.Series, comicInfo.Number)
}

// TestExtractZipMetadataWithoutComicInfo tests .cbz without ComicInfo.xml
func TestExtractZipMetadataWithoutComicInfo(t *testing.T) {
	tmpDir := t.TempDir()
	cbzFile := filepath.Join(tmpDir, "test_no_metadata.cbz")

	// Create a ZIP without ComicInfo.xml
	if err := createTestCBZWithoutMetadata(cbzFile); err != nil {
		t.Fatalf("Failed to create test CBZ: %v", err)
	}

	// Test metadata extraction
	comicInfo, coverImage, err := extractZipMetadata(cbzFile)
	if err != nil {
		t.Fatalf("extractZipMetadata failed: %v", err)
	}

	// Should fall back to filename
	if comicInfo.Title != "test_no_metadata" {
		t.Errorf("Expected title 'test_no_metadata', got '%s'", comicInfo.Title)
	}

	// Should still extract cover
	if len(coverImage) == 0 {
		t.Error("Cover image not extracted")
	}

	t.Logf("ZIP without ComicInfo.xml test passed: title=%s (fallback)", comicInfo.Title)
}

// TestExtractRarMetadata tests .cbr (RAR) metadata extraction
func TestExtractRarMetadata(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping RAR test in short mode (requires test archive)")
	}

	// Note: Creating RAR archives requires additional libraries
	// This test will use a pre-created test file if available
	// For now, we'll test with a fallback

	tmpDir := t.TempDir()
	cbrFile := filepath.Join(tmpDir, "test.cbr")

	// Create a mock RAR file (not a real RAR, just for error handling test)
	if err := os.WriteFile(cbrFile, []byte("not a real rar"), 0644); err != nil {
		t.Fatalf("Failed to create mock RAR: %v", err)
	}

	// Test error handling
	_, _, err := extractRarMetadata(cbrFile)
	if err == nil {
		t.Error("Expected error for invalid RAR file, got nil")
	}

	t.Logf("RAR error handling test passed")
}

// TestExtract7ZipMetadata tests .cb7 (7-Zip) metadata extraction
func TestExtract7ZipMetadata(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping 7-Zip test in short mode (requires test archive)")
	}

	tmpDir := t.TempDir()
	cb7File := filepath.Join(tmpDir, "test.cb7")

	// Create a mock 7-Zip file for error handling test
	if err := os.WriteFile(cb7File, []byte("not a real 7z"), 0644); err != nil {
		t.Fatalf("Failed to create mock 7z: %v", err)
	}

	// Test error handling
	_, _, err := extract7ZipMetadata(cb7File)
	if err == nil {
		t.Error("Expected error for invalid 7-Zip file, got nil")
	}

	t.Logf("7-Zip error handling test passed")
}

// TestExtractTarMetadata tests .cbt (TAR) metadata extraction
func TestExtractTarMetadata(t *testing.T) {
	tmpDir := t.TempDir()
	cbtFile := filepath.Join(tmpDir, "test.cbt")

	// Create a test TAR archive with ComicInfo.xml and cover image
	if err := createTestCBT(cbtFile); err != nil {
		t.Fatalf("Failed to create test CBT: %v", err)
	}

	// Test metadata extraction
	comicInfo, coverImage, err := extractTarMetadata(cbtFile)
	if err != nil {
		t.Fatalf("extractTarMetadata failed: %v", err)
	}

	// Verify metadata
	if comicInfo == nil {
		t.Fatal("comicInfo is nil")
	}

	if comicInfo.Title != "Test Tar Comic" {
		t.Errorf("Expected title 'Test Tar Comic', got '%s'", comicInfo.Title)
	}

	// Verify cover image
	if len(coverImage) == 0 {
		t.Error("Cover image not extracted")
	}

	t.Logf("TAR metadata extraction test passed: title=%s", comicInfo.Title)
}

// TestExtractTarGzMetadata tests .tar.gz metadata extraction
func TestExtractTarGzMetadata(t *testing.T) {
	tmpDir := t.TempDir()
	targzFile := filepath.Join(tmpDir, "test.tar.gz")

	// Create a test TAR.GZ archive
	if err := createTestTarGz(targzFile); err != nil {
		t.Fatalf("Failed to create test TAR.GZ: %v", err)
	}

	// Test metadata extraction
	comicInfo, coverImage, err := extractTarMetadata(targzFile)
	if err != nil {
		t.Fatalf("extractTarMetadata failed for tar.gz: %v", err)
	}

	// Verify metadata
	if comicInfo == nil {
		t.Fatal("comicInfo is nil")
	}

	if comicInfo.Title != "Test Tar Gz Comic" {
		t.Errorf("Expected title 'Test Tar Gz Comic', got '%s'", comicInfo.Title)
	}

	// Verify cover image
	if len(coverImage) == 0 {
		t.Error("Cover image not extracted")
	}

	t.Logf("TAR.GZ metadata extraction test passed: title=%s", comicInfo.Title)
}

// TestExtractComicMetadata tests the router function
func TestExtractComicMetadata(t *testing.T) {
	tests := []struct {
		name          string
		fileExt       string
		createFunc    func(string) error
		shouldFail    bool
		expectedTitle string
	}{
		{
			name:          "CBZ file",
			fileExt:       ".cbz",
			createFunc:    func(path string) error { return createTestCBZ(path) },
			shouldFail:    false,
			expectedTitle: "Test Comic Title",
		},
		{
			name:          "CBT file",
			fileExt:       ".cbt",
			createFunc:    func(path string) error { return createTestCBT(path) },
			shouldFail:    false,
			expectedTitle: "Test Tar Comic",
		},
		{
			name:          "Unsupported format",
			fileExt:       ".xyz",
			createFunc:    func(path string) error { return os.WriteFile(path, []byte("test"), 0644) },
			shouldFail:    true,
			expectedTitle: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tmpDir := t.TempDir()
			testFile := filepath.Join(tmpDir, "test"+tt.fileExt)

			if err := tt.createFunc(testFile); err != nil {
				t.Fatalf("Failed to create test file: %v", err)
			}

			comicInfo, coverImage, err := extractComicMetadata(testFile)

			if tt.shouldFail {
				if err == nil {
					t.Error("Expected error, got nil")
				}
				return
			}

			if err != nil {
				t.Fatalf("extractComicMetadata failed: %v", err)
			}

			if comicInfo == nil {
				t.Fatal("comicInfo is nil")
			}

			if comicInfo.Title != tt.expectedTitle {
				t.Errorf("Expected title '%s', got '%s'", tt.expectedTitle, comicInfo.Title)
			}

			if len(coverImage) == 0 {
				t.Error("Cover image not extracted")
			}

			t.Logf("Test passed: title=%s, cover=%d bytes", comicInfo.Title, len(coverImage))
		})
	}
}

// TestIsImageFile tests image file detection
func TestIsImageFile(t *testing.T) {
	tests := []struct {
		name     string
		filename string
		expected bool
	}{
		{"JPG file", "cover.jpg", true},
		{"JPEG file", "cover.jpeg", true},
		{"PNG file", "cover.png", true},
		{"GIF file", "cover.gif", true},
		{"Uppercase JPG", "cover.JPG", true},
		{"Mixed case", "cover.JpG", true},
		{"XML file", "ComicInfo.xml", false},
		{"Text file", "readme.txt", false},
		{"No extension", "cover", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isImageFile(tt.filename)
			if result != tt.expected {
				t.Errorf("Expected %v for %s, got %v", tt.expected, tt.filename, result)
			}
		})
	}
}

// BenchmarkExtractZipMetadata benchmarks ZIP extraction
func BenchmarkExtractZipMetadata(b *testing.B) {
	tmpDir := b.TempDir()
	cbzFile := filepath.Join(tmpDir, "bench.cbz")

	if err := createTestCBZ(cbzFile); err != nil {
		b.Fatalf("Failed to create test CBZ: %v", err)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _, _ = extractZipMetadata(cbzFile)
	}
}

// Helper functions to create test archives

// createTestCBZ creates a test ZIP archive with ComicInfo.xml and cover image
func createTestCBZ(path string) error {
	file, err := os.Create(path)
	if err != nil {
		return err
	}
	defer file.Close()

	zipWriter := zip.NewWriter(file)
	defer zipWriter.Close()

	// Add ComicInfo.xml
	comicInfo := ComicInfo{
		Title:     "Test Comic Title",
		Series:    "Test Series",
		Number:    1,
		Volume:    1,
		Publisher: "Test Publisher",
		Year:      2024,
		Month:     1,
		Day:       1,
		Writer:    "Test Writer",
		Penciller: "Test Penciller",
		Inker:     "Test Inker",
		Genre:     "Test Genre",
	}

	xmlData, err := xml.Marshal(comicInfo)
	if err != nil {
		return err
	}

	writer, err := zipWriter.Create("ComicInfo.xml")
	if err != nil {
		return err
	}

	if _, err := writer.Write(xmlData); err != nil {
		return err
	}

	// Create a simple valid PNG image (1x1 red pixel) using image/png package
	img := image.NewRGBA(image.Rect(0, 0, 1, 1))
	img.Set(0, 0, color.RGBA{255, 0, 0, 255})

	var pngBuf bytes.Buffer
	if err := png.Encode(&pngBuf, img); err != nil {
		return err
	}
	coverPNG := pngBuf.Bytes()

	// Write cover with .jpg extension (even though it's PNG data, for testing extension handling)
	writer, err = zipWriter.Create("cover.jpg")
	if err != nil {
		return err
	}

	if _, err := writer.Write(coverPNG); err != nil {
		return err
	}

	// Add a page image
	writer, err = zipWriter.Create("page001.jpg")
	if err != nil {
		return err
	}

	if _, err := writer.Write(coverPNG); err != nil {
		return err
	}

	return nil
}

// createTestCBZWithoutMetadata creates a ZIP without ComicInfo.xml
func createTestCBZWithoutMetadata(path string) error {
	file, err := os.Create(path)
	if err != nil {
		return err
	}
	defer file.Close()

	zipWriter := zip.NewWriter(file)
	defer zipWriter.Close()

	// Create a simple valid PNG image
	img := image.NewRGBA(image.Rect(0, 0, 1, 1))
	img.Set(0, 0, color.RGBA{0, 0, 255, 255})

	var pngBuf bytes.Buffer
	if err := png.Encode(&pngBuf, img); err != nil {
		return err
	}
	coverPNG := pngBuf.Bytes()

	writer, err := zipWriter.Create("cover.jpg")
	if err != nil {
		return err
	}

	if _, err := writer.Write(coverPNG); err != nil {
		return err
	}

	return nil
}

// createTestCBT creates a test TAR archive
func createTestCBT(path string) error {
	file, err := os.Create(path)
	if err != nil {
		return err
	}
	defer file.Close()

	tarWriter := tar.NewWriter(file)
	defer tarWriter.Close()

	// Add ComicInfo.xml
	comicInfo := ComicInfo{
		Title:     "Test Tar Comic",
		Series:    "Test Tar Series",
		Number:    5,
		Publisher: "Test Publisher",
	}

	xmlData, err := xml.Marshal(comicInfo)
	if err != nil {
		return err
	}

	header := &tar.Header{
		Name:    "ComicInfo.xml",
		Mode:    0644,
		Size:    int64(len(xmlData)),
		ModTime: time.Now(),
	}

	if err := tarWriter.WriteHeader(header); err != nil {
		return err
	}

	if _, err := tarWriter.Write(xmlData); err != nil {
		return err
	}

	// Create a simple valid PNG image
	img := image.NewRGBA(image.Rect(0, 0, 1, 1))
	img.Set(0, 0, color.RGBA{0, 255, 0, 255})

	var pngBuf bytes.Buffer
	if err := png.Encode(&pngBuf, img); err != nil {
		return err
	}
	coverPNG := pngBuf.Bytes()

	header = &tar.Header{
		Name:    "cover.jpg",
		Mode:    0644,
		Size:    int64(len(coverPNG)),
		ModTime: time.Now(),
	}

	if err := tarWriter.WriteHeader(header); err != nil {
		return err
	}

	if _, err := tarWriter.Write(coverPNG); err != nil {
		return err
	}

	return nil
}

// createTestTarGz creates a test TAR.GZ archive
func createTestTarGz(path string) error {
	// First create the TAR content in memory
	var tarBuffer bytes.Buffer
	tarWriter := tar.NewWriter(&tarBuffer)

	comicInfo := ComicInfo{
		Title:  "Test Tar Gz Comic",
		Series: "Test Tar Gz Series",
		Number: 10,
	}

	xmlData, err := xml.Marshal(comicInfo)
	if err != nil {
		return err
	}

	header := &tar.Header{
		Name:    "ComicInfo.xml",
		Mode:    0644,
		Size:    int64(len(xmlData)),
		ModTime: time.Now(),
	}

	if err := tarWriter.WriteHeader(header); err != nil {
		return err
	}

	if _, err := tarWriter.Write(xmlData); err != nil {
		return err
	}

	// Create a simple valid PNG image
	img := image.NewRGBA(image.Rect(0, 0, 1, 1))
	img.Set(0, 0, color.RGBA{255, 255, 0, 255})

	var pngBuf bytes.Buffer
	if err := png.Encode(&pngBuf, img); err != nil {
		return err
	}
	coverPNG := pngBuf.Bytes()

	header = &tar.Header{
		Name:    "cover.jpg",
		Mode:    0644,
		Size:    int64(len(coverPNG)),
		ModTime: time.Now(),
	}

	if err := tarWriter.WriteHeader(header); err != nil {
		return err
	}

	if _, err := tarWriter.Write(coverPNG); err != nil {
		return err
	}

	tarWriter.Close()

	// Now compress the TAR with gzip
	file, err := os.Create(path)
	if err != nil {
		return err
	}
	defer file.Close()

	gzWriter := gzip.NewWriter(file)
	defer gzWriter.Close()

	if _, err := gzWriter.Write(tarBuffer.Bytes()); err != nil {
		return err
	}

	return nil
}

// Example usage
func Example_extractComicMetadata() {
	// Extract metadata from a comic archive
	comicInfo, coverImage, err := extractComicMetadata("/path/to/comic.cbz")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Title: %s\n", comicInfo.Title)
	fmt.Printf("Series: %s\n", comicInfo.Series)
	fmt.Printf("Issue: %d\n", comicInfo.Number)
	fmt.Printf("Cover size: %d bytes\n", len(coverImage))
}

func TestNormalizeMangaType(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"Unknown", "Unknown", "unknown"},
		{"No", "No", "no"},
		{"Yes", "Yes", "yes"},
		{"YesAndRightToLeft", "YesAndRightToLeft", "yes_and_right_to_left"},
		{"Lowercase yesandrighttoleft", "yesandrighttoleft", "yes_and_right_to_left"},
		{"With spaces", "Yes And Right To Left", "yes_and_right_to_left"},
		{"Invalid", "invalid", "unknown"},
		{"Empty", "", "unknown"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := normalizeMangaType(tt.input)
			if result != tt.expected {
				t.Errorf("normalizeMangaType(%q) = %q; want %q", tt.input, result, tt.expected)
			}
		})
	}
}

func TestDetermineReadingDirection(t *testing.T) {
	tests := []struct {
		name     string
		manga    string
		language string
		tags     string
		genre    string
		expected string
	}{
		{"Explicit RTL", "YesAndRightToLeft", "en", "", "", "rtl"},
		{"Explicit LTR (Yes)", "Yes", "ja", "", "", "ltr"},
		{"Explicit LTR (No)", "No", "en", "", "", "ltr"},
		{"Japanese heuristic", "Unknown", "ja", "", "", "rtl"},
		{"Japanese with full code", "Unknown", "jpn", "", "", "rtl"},
		{"Webtoon Korean", "Unknown", "ko", "Webtoon", "", "vertical"},
		{"Manhwa in tags", "Unknown", "ko", "", "Manhwa", "vertical"},
		{"Manga + Japanese", "Unknown", "ja", "Manga", "", "rtl"},
		{"Western default", "Unknown", "en", "", "", "ltr"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			comicInfo := &ComicInfo{
				Manga:       tt.manga,
				LanguageISO: tt.language,
				Tags:        tt.tags,
				Genre:       tt.genre,
			}
			result := determineReadingDirection(comicInfo)
			if result != tt.expected {
				t.Errorf("determineReadingDirection() = %q; want %q", result, tt.expected)
			}
		})
	}
}
