package installer

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"gitgud.io/mike/mpv-manager/pkg/constants"
	"gitgud.io/mike/mpv-manager/pkg/log"
)

// ExtractArchiveWithOutput extracts an archive to the specified destination with output logging
func (i *Installer) ExtractArchiveWithOutput(cr *CommandRunner, src, dest string) error {
	cr.outputChan <- fmt.Sprintf("Extracting: %s", src)

	var err error
	ext := strings.ToLower(filepath.Ext(src))

	switch {
	case ext == ".zip":
		err = i.unzipWithOutput(cr, src, dest)
	case ext == ".7z":
		err = i.extract7zWithOutput(cr, src, dest)
	case strings.HasSuffix(src, ".tar.gz") || strings.HasSuffix(src, ".tar.xz"):
		err = i.extractTarWithOutput(cr, src, dest)
	default:
		err = fmt.Errorf("unsupported archive format: %s", ext)
	}

	if err != nil {
		cr.outputChan <- fmt.Sprintf("Error: Extraction failed: %v", err)
		return err
	}

	cr.outputChan <- fmt.Sprintf("Extracted to: %s", dest)
	return nil
}

// ExtractArchiveContentsWithOutput extracts archive contents only (skips top-level directory)
// Downloads to temp dir, extracts, then moves contents to destination
func (i *Installer) ExtractArchiveContentsWithOutput(cr *CommandRunner, url, dest string) error {
	// Create temp directory
	tempDir, err := os.MkdirTemp("", "mpv-extract")
	if err != nil {
		return fmt.Errorf("failed to create temp directory: %w", err)
	}
	defer os.RemoveAll(tempDir)

	cr.outputChan <- fmt.Sprintf("Downloading to temp directory: %s", tempDir)

	// Download archive to temp dir
	archiveName := filepath.Base(url)
	archivePath := filepath.Join(tempDir, archiveName)
	if err := i.DownloadFileWithProgressToChannel(cr, url, archivePath); err != nil {
		return err
	}

	cr.outputChan <- "Extracting archive to temp directory..."
	if err := i.ExtractArchiveWithOutput(cr, archivePath, tempDir); err != nil {
		return err
	}

	// Find extracted directory (should be single top-level dir)
	entries, err := os.ReadDir(tempDir)
	if err != nil {
		return err
	}

	var extractedDir string
	var hasTopLevelDir bool

	for _, entry := range entries {
		if entry.IsDir() && entry.Name() != archiveName && !strings.HasPrefix(entry.Name(), archiveName) {
			if extractedDir == "" {
				extractedDir = filepath.Join(tempDir, entry.Name())
				hasTopLevelDir = true
			}
		}
	}

	var sourceDir string
	if hasTopLevelDir {
		sourceDir = extractedDir
		cr.outputChan <- fmt.Sprintf("Moving contents from %s to %s", filepath.Base(extractedDir), dest)
	} else {
		sourceDir = tempDir
		cr.outputChan <- fmt.Sprintf("Moving files from temp to %s", dest)
	}

	// Ensure destination exists
	if err := i.EnsureDirWithOutput(cr, dest); err != nil {
		return err
	}

	// Move all contents from source to destination
	var fileCount int
	err = filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		relPath, err := filepath.Rel(sourceDir, path)
		if err != nil {
			return err
		}

		if relPath == "." {
			return nil
		}

		destPath := filepath.Join(dest, relPath)

		if info.IsDir() {
			return os.MkdirAll(destPath, info.Mode())
		}

		fileCount++
		return i.copyFileQuiet(path, destPath, info.Mode())
	})

	if err != nil {
		return fmt.Errorf("failed to move files: %w", err)
	}

	cr.outputChan <- fmt.Sprintf("Moved %d files successfully", fileCount)
	return nil
}

// SetSevenZipPath sets a custom path to the 7zip executable
func (i *Installer) SetSevenZipPath(path string) {
	i.sevenZipPath = path
}

// unzipWithOutput extracts a ZIP archive with output logging
func (i *Installer) unzipWithOutput(cr *CommandRunner, src, dest string) error {
	if runtime.GOOS == "windows" {
		ext := strings.ToLower(filepath.Ext(src))
		if ext == ".zip" {
			psCmd := fmt.Sprintf("Expand-Archive -Path '%s' -DestinationPath '%s' -Force", src, dest)
			cmd := cr.RunCommand("powershell", "-Command", psCmd)
			return cmd
		}
		sevenZip := i.sevenZipPath
		if sevenZip == "" {
			sevenZip = Find7zip()
		}
		if sevenZip == "" {
			return fmt.Errorf(constants.Err7zipNotFoundDetailed)
		}
		cmd := cr.RunCommand(sevenZip, "x", src, fmt.Sprintf("-o%s", dest), "-y")
		return cmd
	}

	cmd := cr.RunCommand("unzip", "-o", "-q", src, "-d", dest)
	return cmd
}

// extract7zWithOutput extracts a 7z archive with output logging
func (i *Installer) extract7zWithOutput(cr *CommandRunner, src, dest string) error {
	sevenZip := i.sevenZipPath
	if sevenZip == "" {
		sevenZip = Find7zip()
	}
	if sevenZip == "" {
		return fmt.Errorf(constants.Err7zipNotFoundDetailed)
	}

	cmd := cr.RunCommand(sevenZip, "x", src, fmt.Sprintf("-o%s", dest), "-y")
	return cmd
}

// extractTarWithOutput extracts a tar archive (optionally gzipped) with output logging
func (i *Installer) extractTarWithOutput(cr *CommandRunner, src, dest string) error {
	if strings.HasSuffix(src, ".tar.gz") {
		cmd := cr.RunCommand("tar", "xzf", src, "-C", dest)
		return cmd
	}
	cmd := cr.RunCommand("tar", "xf", src, "-C", dest)
	return cmd
}

// extractZip extracts a ZIP archive (no output logging)
func (i *Installer) extractZip(src, dest string) error {
	if runtime.GOOS == "windows" {
		sevenZip := i.sevenZipPath
		if sevenZip == "" {
			sevenZip = Find7zip()
		}
		if sevenZip == "" {
			log.Error("7zip not found. Cannot extract ZIP on Windows")
			return fmt.Errorf("7zip not found. Cannot extract ZIP on Windows")
		}
		cmd := exec.Command(sevenZip, "x", src, fmt.Sprintf("-o%s", dest), "-y")
		if err := cmd.Run(); err != nil {
			log.Error(fmt.Sprintf("7-ZIP extraction failed: %s", err.Error()))
			return err
		}
		return nil
	}

	cmd := exec.Command("unzip", "-q", src, "-d", dest)
	if err := cmd.Run(); err != nil {
		log.Error(fmt.Sprintf("unzip extraction failed: %s", err.Error()))
		return err
	}
	return nil
}

// ExtractArchive extracts an archive to the specified destination (no output logging)
func (i *Installer) ExtractArchive(src, dest string) error {
	ext := strings.ToLower(filepath.Ext(src))

	switch ext {
	case ".zip":
		return i.extractZip(src, dest)
	case ".7z", ".7zip":
		return i.extract7z(src, dest)
	case ".tar":
		return i.extractTar(src, dest)
	case ".gz":
		if strings.HasSuffix(src, ".tar.gz") {
			return i.extractTarGz(src, dest)
		}
		return i.extractGz(src, dest)
	case ".xz":
		return i.extractXz(src, dest)
	case ".dmg":
		return fmt.Errorf("DMG files must be opened manually")
	case ".exe":
		return fmt.Errorf("EXE files must be executed manually")
	default:
		return fmt.Errorf("unsupported archive format: %s", ext)
	}
}

// extract7z extracts a 7z archive (no output logging)
func (i *Installer) extract7z(src, dest string) error {
	sevenZip := Find7zip()
	if sevenZip == "" {
		return fmt.Errorf(constants.Err7zipNotFoundDetailed)
	}

	cmd := exec.Command(sevenZip, "x", src, fmt.Sprintf("-o%s", dest), "-y")
	return cmd.Run()
}

// extractTar extracts a tar archive (no output logging)
func (i *Installer) extractTar(src, dest string) error {
	cmd := exec.Command("tar", "xf", src, "-C", dest)
	return cmd.Run()
}

// extractTarGz extracts a tar.gz archive (no output logging)
func (i *Installer) extractTarGz(src, dest string) error {
	cmd := exec.Command("tar", "xzf", src, "-C", dest)
	return cmd.Run()
}

// extractGz extracts a gz archive (no Output logging)
func (i *Installer) extractGz(src, dest string) error {
	cmd := exec.Command("gzip", "-d", "-c", src)
	output, err := cmd.Output()
	if err != nil {
		log.Error(fmt.Sprintf("gzip extraction failed for %s: %s", src, err.Error()))
		return fmt.Errorf("gzip extraction failed: %w", err)
	}

	destPath := filepath.Join(dest, strings.TrimSuffix(filepath.Base(src), ".gz"))
	if err := os.MkdirAll(dest, 0755); err != nil {
		log.Error(fmt.Sprintf("Failed to create directory %s: %s", dest, err.Error()))
		return err
	}

	if err := os.WriteFile(destPath, output, 0755); err != nil {
		log.Error(fmt.Sprintf("Failed to write file %s: %s", destPath, err.Error()))
		return err
	}
	return nil
}

// extractXz extracts an xz archive (no output logging)
func (i *Installer) extractXz(src, dest string) error {
	cmd := exec.Command("xz", "-d", "-c", src)
	output, err := cmd.Output()
	if err != nil {
		log.Error(fmt.Sprintf("xz extraction failed for %s: %s", src, err.Error()))
		return fmt.Errorf("xz extraction failed: %w", err)
	}

	destPath := filepath.Join(dest, strings.TrimSuffix(filepath.Base(src), ".xz"))
	if err := os.MkdirAll(dest, 0755); err != nil {
		log.Error(fmt.Sprintf("Failed to create directory %s: %s", dest, err.Error()))
		return err
	}

	if err := os.WriteFile(destPath, output, 0755); err != nil {
		log.Error(fmt.Sprintf("Failed to write file %s: %s", destPath, err.Error()))
		return err
	}
	return nil
}
