package processors

import (
	"archive/zip"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/sirupsen/logrus"
)

// AssetDownloader handles downloading and processing assets (icons, images)
type AssetDownloader struct {
	logger     *logrus.Logger
	httpClient *http.Client
	cacheDir   string
}

// NewAssetDownloader creates a new asset downloader
func NewAssetDownloader(logger *logrus.Logger, cacheDir string) *AssetDownloader {
	return &AssetDownloader{
		logger: logger,
		httpClient: &http.Client{
			Timeout: 60 * time.Second,
		},
		cacheDir: cacheDir,
	}
}

// DownloadFile downloads a file from URL with retry logic
func (a *AssetDownloader) DownloadFile(url string, outputPath string) error {
	maxRetries := 3
	var lastErr error

	for attempt := 1; attempt <= maxRetries; attempt++ {
		err := a.downloadAttempt(url, outputPath)
		if err == nil {
			return nil
		}

		lastErr = err
		a.logger.WithFields(logrus.Fields{
			"url":     url,
			"attempt": attempt,
			"error":   err,
		}).Warn("Download attempt failed, retrying...")

		if attempt < maxRetries {
			time.Sleep(time.Second * time.Duration(attempt))
		}
	}

	return fmt.Errorf("failed to download after %d attempts: %w", maxRetries, lastErr)
}

func (a *AssetDownloader) downloadAttempt(url string, outputPath string) error {
	// Create request
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return err
	}

	// Download
	resp, err := a.httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("bad status: %s", resp.Status)
	}

	// Ensure output directory exists
	if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
		return err
	}

	// Create output file
	out, err := os.Create(outputPath)
	if err != nil {
		return err
	}
	defer out.Close()

	// Copy data
	_, err = io.Copy(out, resp.Body)
	return err
}

// DownloadImage downloads an image and returns the path
func (a *AssetDownloader) DownloadImage(url, buildID, imageName string) (string, error) {
	if url == "" {
		return "", nil
	}

	// Determine file extension from URL
	ext := filepath.Ext(url)
	if ext == "" {
		ext = ".png" // Default to PNG
	}

	// Create cache path
	filename := fmt.Sprintf("%s_%s%s", buildID, imageName, ext)
	outputPath := filepath.Join(a.cacheDir, "images", filename)

	// Download
	if err := a.DownloadFile(url, outputPath); err != nil {
		return "", fmt.Errorf("failed to download image %s: %w", url, err)
	}

	a.logger.WithFields(logrus.Fields{
		"url":    url,
		"output": outputPath,
	}).Debug("Downloaded image")

	return outputPath, nil
}

// ExtractIconZip extracts icons from a ZIP file (icon.kitchen format)
func (a *AssetDownloader) ExtractIconZip(zipPath, extractDir string) error {
	// Open ZIP file
	reader, err := zip.OpenReader(zipPath)
	if err != nil {
		return fmt.Errorf("failed to open ZIP: %w", err)
	}
	defer reader.Close()

	// Extract all files
	for _, file := range reader.File {
		if err := a.extractIconFile(file, extractDir); err != nil {
			return err
		}
	}

	a.logger.WithFields(logrus.Fields{
		"zip":     zipPath,
		"extract": extractDir,
	}).Debug("Extracted icon ZIP")

	return nil
}

func (a *AssetDownloader) extractIconFile(file *zip.File, extractDir string) error {
	// Prevent ZipSlip vulnerability
	filePath := filepath.Join(extractDir, file.Name)
	if !strings.HasPrefix(filePath, filepath.Clean(extractDir)+string(os.PathSeparator)) {
		return fmt.Errorf("illegal file path: %s", file.Name)
	}

	// Create directory if needed
	if file.FileInfo().IsDir() {
		return os.MkdirAll(filePath, 0755)
	}

	// Ensure parent directory exists
	if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
		return err
	}

	// Extract file
	srcFile, err := file.Open()
	if err != nil {
		return err
	}
	defer srcFile.Close()

	destFile, err := os.Create(filePath)
	if err != nil {
		return err
	}
	defer destFile.Close()

	_, err = io.Copy(destFile, srcFile)
	return err
}

// ProcessIconsForAndroid processes downloaded icons and places them in mipmap directories
func (a *AssetDownloader) ProcessIconsForAndroid(iconDir, projectDir string) error {
	// Icon densities and their expected directories
	densities := []string{"mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"}

	for _, density := range densities {
		// Try icon.kitchen standard structure: android/res/mipmap-{density}/
		srcDir := filepath.Join(iconDir, "android", "res", fmt.Sprintf("mipmap-%s", density))

		if _, err := os.Stat(srcDir); os.IsNotExist(err) {
			// Fallback 1: android/mipmap-{density}/
			srcDir = filepath.Join(iconDir, "android", fmt.Sprintf("mipmap-%s", density))

			if _, err := os.Stat(srcDir); os.IsNotExist(err) {
				// Fallback 2: mipmap-{density}/ (flat structure)
				srcDir = filepath.Join(iconDir, fmt.Sprintf("mipmap-%s", density))
			}
		}

		// Destination directory in project
		destDir := filepath.Join(projectDir, "app", "src", "main", "res", fmt.Sprintf("mipmap-%s", density))

		// Copy icons
		if err := a.copyIcons(srcDir, destDir); err != nil {
			return fmt.Errorf("failed to copy %s icons: %w", density, err)
		}
	}

	a.logger.WithFields(logrus.Fields{
		"source": iconDir,
		"dest":   projectDir,
	}).Debug("Processed Android icons")

	return nil
}

func (a *AssetDownloader) copyIcons(srcDir, destDir string) error {
	// Check if source exists
	if _, err := os.Stat(srcDir); os.IsNotExist(err) {
		a.logger.WithField("dir", srcDir).Debug("Icon directory not found, skipping")
		return nil
	}

	// Ensure destination exists
	if err := os.MkdirAll(destDir, 0755); err != nil {
		return err
	}

	// Delete existing ic_launcher files to prevent conflicts (XML and PNG)
	if err := a.cleanExistingIcons(destDir); err != nil {
		return fmt.Errorf("failed to clean existing icons: %w", err)
	}

	// Read source directory
	files, err := os.ReadDir(srcDir)
	if err != nil {
		return err
	}

	// Copy icon files
	for _, file := range files {
		if file.IsDir() {
			continue
		}

		// Only copy ic_launcher files
		if strings.HasPrefix(file.Name(), "ic_launcher") {
			srcPath := filepath.Join(srcDir, file.Name())
			destPath := filepath.Join(destDir, file.Name())

			if err := a.copyFile(srcPath, destPath); err != nil {
				return fmt.Errorf("failed to copy %s: %w", file.Name(), err)
			}
		}
	}

	return nil
}

// cleanExistingIcons removes existing ic_launcher files to prevent duplicate resources
// Keeps ic_launcher_round as fallback since not all icon ZIPs include it
func (a *AssetDownloader) cleanExistingIcons(destDir string) error {
	// Check if destination exists
	if _, err := os.Stat(destDir); os.IsNotExist(err) {
		return nil
	}

	// Read destination directory
	files, err := os.ReadDir(destDir)
	if err != nil {
		return err
	}

	// Delete only base ic_launcher files (not ic_launcher_round)
	// This prevents XML/PNG conflicts while keeping round icon fallback
	for _, file := range files {
		if file.IsDir() {
			continue
		}

		// Delete ic_launcher.xml and ic_launcher.png but NOT ic_launcher_round.*
		if strings.HasPrefix(file.Name(), "ic_launcher.") {
			filePath := filepath.Join(destDir, file.Name())
			if err := os.Remove(filePath); err != nil {
				a.logger.WithFields(logrus.Fields{
					"file":  filePath,
					"error": err,
				}).Warn("Failed to delete existing icon file")
			}
		}
	}

	return nil
}

func (a *AssetDownloader) copyFile(src, dest string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	destFile, err := os.Create(dest)
	if err != nil {
		return err
	}
	defer destFile.Close()

	_, err = io.Copy(destFile, srcFile)
	return err
}

// ProcessSplashImages copies downloaded splash images to Android drawable directory
func (a *AssetDownloader) ProcessSplashImages(logoPath, bgPath, projectDir string) error {
	drawableDir := filepath.Join(projectDir, "app", "src", "main", "res", "drawable")

	// Ensure drawable directory exists
	if err := os.MkdirAll(drawableDir, 0755); err != nil {
		return fmt.Errorf("failed to create drawable directory: %w", err)
	}

	// Copy splash logo if provided
	if logoPath != "" {
		destPath := filepath.Join(drawableDir, "splash_logo.png")
		if err := a.copyFile(logoPath, destPath); err != nil {
			return fmt.Errorf("failed to copy splash logo: %w", err)
		}
		a.logger.WithFields(logrus.Fields{
			"source": logoPath,
			"dest":   destPath,
		}).Debug("Copied splash logo to drawable")
	}

	// Copy splash background if provided
	if bgPath != "" {
		destPath := filepath.Join(drawableDir, "splash_background.png")
		if err := a.copyFile(bgPath, destPath); err != nil {
			return fmt.Errorf("failed to copy splash background: %w", err)
		}
		a.logger.WithFields(logrus.Fields{
			"source": bgPath,
			"dest":   destPath,
		}).Debug("Copied splash background to drawable")
	}

	return nil
}

// ProcessDrawerImages copies drawer logo and background to drawable directory
func (a *AssetDownloader) ProcessDrawerImages(logoLightPath, logoDarkPath, bgPath, projectDir string) error {
	drawableDir := filepath.Join(projectDir, "app", "src", "main", "res", "drawable")

	// Ensure drawable directory exists
	if err := os.MkdirAll(drawableDir, 0755); err != nil {
		return fmt.Errorf("failed to create drawable directory: %w", err)
	}

	// Copy light mode logo if provided
	if logoLightPath != "" {
		destPath := filepath.Join(drawableDir, "drawer_logo_light.png")
		if err := a.copyFile(logoLightPath, destPath); err != nil {
			return fmt.Errorf("failed to copy drawer light logo: %w", err)
		}
		a.logger.WithFields(logrus.Fields{
			"source": logoLightPath,
			"dest":   destPath,
		}).Debug("Copied drawer light logo to drawable")
	}

	// Copy dark mode logo if provided
	if logoDarkPath != "" {
		destPath := filepath.Join(drawableDir, "drawer_logo_dark.png")
		if err := a.copyFile(logoDarkPath, destPath); err != nil {
			return fmt.Errorf("failed to copy drawer dark logo: %w", err)
		}
		a.logger.WithFields(logrus.Fields{
			"source": logoDarkPath,
			"dest":   destPath,
		}).Debug("Copied drawer dark logo to drawable")
	}

	// Copy drawer background if provided
	if bgPath != "" {
		destPath := filepath.Join(drawableDir, "drawer_background.png")
		if err := a.copyFile(bgPath, destPath); err != nil {
			return fmt.Errorf("failed to copy drawer background: %w", err)
		}
		a.logger.WithFields(logrus.Fields{
			"source": bgPath,
			"dest":   destPath,
		}).Debug("Copied drawer background to drawable")
	}

	return nil
}

// Cleanup removes temporary files
func (a *AssetDownloader) Cleanup(paths ...string) error {
	for _, path := range paths {
		if err := os.RemoveAll(path); err != nil {
			a.logger.WithField("path", path).Warn("Failed to cleanup path")
		}
	}
	return nil
}
