package downloader

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

	"github.com/fatih/color"
)

const (
	XrayVersion      = "24.12.18"
	V2RayVersion     = "5.22.0"
	XrayDownloadURL  = "https://github.com/XTLS/Xray-core/releases/download/v%s/Xray-windows-64.zip"
	V2RayDownloadURL = "https://github.com/v2fly/v2ray-core/releases/download/v%s/v2ray-windows-64.zip"
)

var (
	green  = color.New(color.FgGreen).SprintFunc()
	yellow = color.New(color.FgYellow).SprintFunc()
	cyan   = color.New(color.FgCyan).SprintFunc()
)

// DownloadCore загружает Xray или V2Ray
func DownloadCore(coreDir, coreName string) error {
	var downloadURL, version, exeName string

	if coreName == "xray" {
		downloadURL = fmt.Sprintf(XrayDownloadURL, XrayVersion)
		version = XrayVersion
		exeName = "xray.exe"
	} else if coreName == "v2ray" {
		downloadURL = fmt.Sprintf(V2RayDownloadURL, V2RayVersion)
		version = V2RayVersion
		exeName = "v2ray.exe"
	} else {
		return fmt.Errorf("неизвестный тип ядра: %s", coreName)
	}

	// Проверяем, существует ли уже
	coreExe := filepath.Join(coreDir, exeName)
	if _, err := os.Stat(coreExe); err == nil {
		return nil
	}

	fmt.Println("\n" + strings.Repeat("=", 60))
	fmt.Printf("%s %s не найден. Начинаю загрузку...\n", yellow("⚠"), strings.ToUpper(coreName))
	fmt.Println(strings.Repeat("=", 60))

	// Создаем директорию
	if err := os.MkdirAll(coreDir, 0755); err != nil {
		return fmt.Errorf("ошибка создания директории: %w", err)
	}

	fmt.Printf("\nВерсия: %s\n", cyan(version))
	fmt.Printf("URL: %s\n\n", downloadURL)

	// Загружаем файл
	zipPath := filepath.Join(coreDir, coreName+".zip")
	if err := downloadFile(zipPath, downloadURL); err != nil {
		return fmt.Errorf("ошибка загрузки: %w", err)
	}

	fmt.Printf("\n%s Загрузка завершена\n", green("✓"))
	fmt.Printf("%s Распаковка архива...\n", yellow("⚙"))

	// Распаковываем
	if err := unzip(zipPath, coreDir); err != nil {
		os.Remove(zipPath)
		return fmt.Errorf("ошибка распаковки: %w", err)
	}

	os.Remove(zipPath)

	fmt.Printf("%s %s успешно установлен в %s\n", green("✓"), strings.ToUpper(coreName), coreDir)
	fmt.Println(strings.Repeat("=", 60))

	return nil
}

// DownloadXray загружает Xray
func DownloadXray(xrayDir string) error {
	return DownloadCore(xrayDir, "xray")
}

// DownloadV2Ray загружает V2Ray
func DownloadV2Ray(v2rayDir string) error {
	return DownloadCore(v2rayDir, "v2ray")
}

// downloadFile загружает файл с прогресс-баром
func downloadFile(filepath string, url string) error {
	out, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer out.Close()

	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("ошибка загрузки: HTTP %d", resp.StatusCode)
	}

	size := resp.ContentLength
	counter := &WriteCounter{Total: size}
	
	_, err = io.Copy(out, io.TeeReader(resp.Body, counter))
	if err != nil {
		return err
	}

	fmt.Println()
	return nil
}

// WriteCounter считает загруженные байты
type WriteCounter struct {
	Total      int64
	Downloaded int64
}

func (wc *WriteCounter) Write(p []byte) (int, error) {
	n := len(p)
	wc.Downloaded += int64(n)
	wc.PrintProgress()
	return n, nil
}

func (wc *WriteCounter) PrintProgress() {
	fmt.Printf("\r%s", strings.Repeat(" ", 60))
	percent := float64(wc.Downloaded) / float64(wc.Total) * 100
	downloaded := formatBytes(wc.Downloaded)
	total := formatBytes(wc.Total)
	fmt.Printf("\rЗагрузка: %s / %s (%.1f%%)", downloaded, total, percent)
}

func formatBytes(bytes int64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

// unzip распаковывает zip архив
func unzip(src, dest string) error {
	r, err := zip.OpenReader(src)
	if err != nil {
		return err
	}
	defer r.Close()

	for _, f := range r.File {
		fpath := filepath.Join(dest, f.Name)

		if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
			return fmt.Errorf("недопустимый путь файла: %s", fpath)
		}

		if f.FileInfo().IsDir() {
			os.MkdirAll(fpath, os.ModePerm)
			continue
		}

		if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
			return err
		}

		outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			return err
		}

		rc, err := f.Open()
		if err != nil {
			outFile.Close()
			return err
		}

		_, err = io.Copy(outFile, rc)
		outFile.Close()
		rc.Close()

		if err != nil {
			return err
		}
	}
	return nil
}

// CheckAndDownloadXray проверяет и загружает Xray
func CheckAndDownloadXray(xrayDir string) error {
	if runtime.GOOS != "windows" {
		return nil
	}

	xrayExe := filepath.Join(xrayDir, "xray.exe")
	if _, err := os.Stat(xrayExe); os.IsNotExist(err) {
		return DownloadXray(xrayDir)
	}

	return nil
}

// CheckAndDownloadV2Ray проверяет и загружает V2Ray
func CheckAndDownloadV2Ray(v2rayDir string) error {
	if runtime.GOOS != "windows" {
		return nil
	}

	v2rayExe := filepath.Join(v2rayDir, "v2ray.exe")
	if _, err := os.Stat(v2rayExe); os.IsNotExist(err) {
		return DownloadV2Ray(v2rayDir)
	}

	return nil
}

// CheckAndDownloadBoth проверяет и загружает оба ядра
func CheckAndDownloadBoth(xrayDir, v2rayDir string) error {
	if runtime.GOOS != "windows" {
		return nil
	}

	// Проверяем Xray
	xrayExe := filepath.Join(xrayDir, "xray.exe")
	xrayExists := true
	if _, err := os.Stat(xrayExe); os.IsNotExist(err) {
		xrayExists = false
	}

	// Проверяем V2Ray
	v2rayExe := filepath.Join(v2rayDir, "v2ray.exe")
	v2rayExists := true
	if _, err := os.Stat(v2rayExe); os.IsNotExist(err) {
		v2rayExists = false
	}

	// Если оба есть, ничего не делаем
	if xrayExists && v2rayExists {
		return nil
	}

	// Загружаем недостающие
	if !xrayExists {
		if err := DownloadXray(xrayDir); err != nil {
			return err
		}
	}

	if !v2rayExists {
		if err := DownloadV2Ray(v2rayDir); err != nil {
			return err
		}
	}

	return nil
}
