package archive

import (
	"archive/zip"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

type ArchiveEntryInfo struct {
	Path  string // Относительный путь внутри архива
	Size  uint64 // Размер файла в байтах (0 для директорий)
	IsDir bool   // Является ли запись директорией
}

var ErrFileNotFoundInArchive = errors.New("file not found in archive")

// CreateArchive создает ZIP-архив по пути targetPath из содержимого sourceDir.
func CreateArchive(sourceDir, targetPath string) error {
	info, err := os.Stat(sourceDir)
	if err != nil {
		return fmt.Errorf("failed to stat source directory %s: %w", sourceDir, err)
	}
	if !info.IsDir() {
		return fmt.Errorf("source %s is not a directory", sourceDir)
	}

	// Создаем файл для ZIP-архива
	outFile, err := os.Create(targetPath)
	if err != nil {
		return fmt.Errorf("failed to create target archive file %s: %w", targetPath, err)
	}
	defer outFile.Close()

	// Создаем zip.Writer
	zipWriter := zip.NewWriter(outFile)
	defer zipWriter.Close()

	// Рекурсивно проходим по директории sourceDir
	err = filepath.Walk(sourceDir, func(filePath string, fileInfo os.FileInfo, walkErr error) error {
		if walkErr != nil {
			return fmt.Errorf("error walking path %s: %w", filePath, walkErr)
		}

		// Пропускаем саму директорию sourceDir
		if filePath == sourceDir {
			return nil
		}

		// Создаем относительный путь для записи в архиве
		relativePath := strings.TrimPrefix(filePath, sourceDir+string(filepath.Separator))
		relativePath = filepath.ToSlash(relativePath)

		// Проверяем, является ли текущий элемент директорией
		if fileInfo.IsDir() {
			// Для директорий добавляем "/" в конец
			_, err = zipWriter.Create(relativePath + "/")
			if err != nil {
				return fmt.Errorf("failed to create directory entry %s in archive: %w", relativePath, err)
			}
			return nil
		}

		// Открываем файл для чтения
		var fileToArchive *os.File
		fileToArchive, err = os.Open(filePath)
		if err != nil {
			return fmt.Errorf("failed to open file %s for archiving: %w", filePath, err)
		}
		defer fileToArchive.Close()

		// Создаем запись в архиве
		var zipEntryWriter io.Writer
		zipEntryWriter, err = zipWriter.Create(relativePath)
		if err != nil {
			return fmt.Errorf("failed to create entry %s in archive: %w", relativePath, err)
		}

		// Копируем содержимое файла в запись архива
		if _, err = io.Copy(zipEntryWriter, fileToArchive); err != nil {
			return fmt.Errorf("failed to copy file content %s to archive: %w", filePath, err)
		}

		return nil
	})

	if err != nil {
		os.Remove(targetPath)
		return fmt.Errorf("failed during directory walk for archiving %s: %w", sourceDir, err)
	}

	return nil
}

// ListArchiveContents читает ZIP-архив и возвращает информацию о его содержимом.
func ListArchiveContents(archivePath string) ([]ArchiveEntryInfo, error) {
	// Открываем ZIP-архив
	zipReader, err := zip.OpenReader(archivePath)
	if err != nil {
		return nil, fmt.Errorf("failed to open archive file %s: %w", archivePath, err)
	}
	defer zipReader.Close()

	var entries []ArchiveEntryInfo

	// Проходим по всем файлам и директориям внутри архива
	for _, file := range zipReader.File {
		entry := ArchiveEntryInfo{
			Path:  file.Name,
			Size:  file.UncompressedSize64,
			IsDir: file.FileInfo().IsDir(),
		}
		entries = append(entries, entry)
	}

	return entries, nil
}

// ExtractFileFromArchive извлекает один файл из ZIP-архива.
func ExtractFileFromArchive(archivePath, filePathInArchive string, writer io.Writer) error {
	// Открываем ZIP-архив
	zipReader, err := zip.OpenReader(archivePath)
	if err != nil {
		return fmt.Errorf("failed to open archive file %s: %w", archivePath, err)
	}
	defer zipReader.Close()

	// Ищем файл в архиве
	for _, file := range zipReader.File {
		if filepath.ToSlash(file.Name) == filepath.ToSlash(filePathInArchive) {
			// Проверяем, что это не директория
			if file.FileInfo().IsDir() {
				return fmt.Errorf("entry %s in archive is a directory", filePathInArchive)
			}

			// Открываем файл для чтения
			readCloser, err := file.Open()
			if err != nil {
				return fmt.Errorf("failed to open file %s in archive: %w", filePathInArchive, err)
			}
			defer readCloser.Close()

			// Копируем содержимое файла в writer
			if _, err := io.Copy(writer, readCloser); err != nil {
				return fmt.Errorf("failed to copy content of %s from archive: %w", filePathInArchive, err)
			}
			return nil
		}
	}

	return ErrFileNotFoundInArchive
}
