package updater

import (
	"archive/zip"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"
)

// Updater - Otomatik güncelleme servisi
type Updater struct {
	mu              sync.RWMutex
	currentVersion  string
	updateURL       string
	checkInterval   time.Duration
	autoUpdate      bool
	autoRestart     bool
	backupEnabled   bool
	updateChannel   string // "stable", "beta", "dev"
	lastCheck       time.Time
	lastUpdate      *UpdateInfo
	downloadPath    string
	backupPath      string
	isUpdating      bool
	progress        UpdateProgress
	stopChan        chan struct{}
	onUpdateAvailable func(*UpdateInfo)
	onUpdateProgress  func(UpdateProgress)
	onUpdateComplete  func(bool, error)
}

// UpdateInfo - Güncelleme bilgisi
type UpdateInfo struct {
	Version       string            `json:"version"`
	ReleaseDate   time.Time         `json:"releaseDate"`
	DownloadURL   string            `json:"downloadUrl"`
	DownloadSize  int64             `json:"downloadSize"`
	Checksum      string            `json:"checksum"` // SHA256
	ReleaseNotes  string            `json:"releaseNotes"`
	Changes       []ChangelogEntry  `json:"changes"`
	MinVersion    string            `json:"minVersion"` // Minimum gerekli versiyon
	Critical      bool              `json:"critical"`   // Kritik güncelleme mi
	Platform      string            `json:"platform"`
	Arch          string            `json:"arch"`
	Dependencies  []string          `json:"dependencies"`
	Signature     string            `json:"signature"` // Dijital imza
	Channel       string            `json:"channel"`
}

// ChangelogEntry - Değişiklik kaydı
type ChangelogEntry struct {
	Type        string `json:"type"` // "feature", "fix", "improvement", "security"
	Description string `json:"description"`
	IssueID     string `json:"issueId,omitempty"`
}

// UpdateProgress - Güncelleme ilerleme durumu
type UpdateProgress struct {
	Stage           string    `json:"stage"` // "checking", "downloading", "extracting", "installing", "restarting"
	StageProgress   float64   `json:"stageProgress"`   // 0-100
	TotalProgress   float64   `json:"totalProgress"`   // 0-100
	DownloadedBytes int64     `json:"downloadedBytes"`
	TotalBytes      int64     `json:"totalBytes"`
	Speed           float64   `json:"speed"`           // Bytes per second
	ETA             int       `json:"eta"`             // Saniye
	Error           string    `json:"error,omitempty"`
	StartTime       time.Time `json:"startTime"`
}

// UpdateManifest - Güncelleme manifest dosyası
type UpdateManifest struct {
	LatestVersion string       `json:"latestVersion"`
	Updates       []UpdateInfo `json:"updates"`
	Channels      []Channel    `json:"channels"`
}

// Channel - Güncelleme kanalı
type Channel struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Latest      string `json:"latest"`
}

// NewUpdater - Yeni güncelleme servisi
func NewUpdater(currentVersion, updateURL string) *Updater {
	homeDir, _ := os.UserHomeDir()

	return &Updater{
		currentVersion: currentVersion,
		updateURL:      updateURL,
		checkInterval:  6 * time.Hour, // Her 6 saatte bir kontrol
		autoUpdate:     false,
		autoRestart:    false,
		backupEnabled:  true,
		updateChannel:  "stable",
		downloadPath:   filepath.Join(homeDir, ".fikoyt-ajan", "updates"),
		backupPath:     filepath.Join(homeDir, ".fikoyt-ajan", "backups"),
		stopChan:       make(chan struct{}),
	}
}

// SetCallbacks - Callback'leri ayarla
func (u *Updater) SetCallbacks(
	onAvailable func(*UpdateInfo),
	onProgress func(UpdateProgress),
	onComplete func(bool, error),
) {
	u.mu.Lock()
	defer u.mu.Unlock()
	u.onUpdateAvailable = onAvailable
	u.onUpdateProgress = onProgress
	u.onUpdateComplete = onComplete
}

// SetAutoUpdate - Otomatik güncelleme ayarla
func (u *Updater) SetAutoUpdate(enabled bool) {
	u.mu.Lock()
	defer u.mu.Unlock()
	u.autoUpdate = enabled
}

// SetAutoRestart - Otomatik yeniden başlatma ayarla
func (u *Updater) SetAutoRestart(enabled bool) {
	u.mu.Lock()
	defer u.mu.Unlock()
	u.autoRestart = enabled
}

// SetChannel - Güncelleme kanalı ayarla
func (u *Updater) SetChannel(channel string) {
	u.mu.Lock()
	defer u.mu.Unlock()
	u.updateChannel = channel
}

// SetCheckInterval - Kontrol aralığı ayarla
func (u *Updater) SetCheckInterval(interval time.Duration) {
	u.mu.Lock()
	defer u.mu.Unlock()
	u.checkInterval = interval
}

// Start - Güncelleme kontrolünü başlat
func (u *Updater) Start() {
	// Dizinleri oluştur
	os.MkdirAll(u.downloadPath, 0755)
	os.MkdirAll(u.backupPath, 0755)

	// İlk kontrol
	go u.CheckForUpdates()

	// Periyodik kontrol
	go u.periodicCheck()
}

// Stop - Güncelleme kontrolünü durdur
func (u *Updater) Stop() {
	close(u.stopChan)
}

// periodicCheck - Periyodik güncelleme kontrolü
func (u *Updater) periodicCheck() {
	ticker := time.NewTicker(u.checkInterval)
	defer ticker.Stop()

	for {
		select {
		case <-u.stopChan:
			return
		case <-ticker.C:
			u.CheckForUpdates()
		}
	}
}

// CheckForUpdates - Güncelleme kontrolü
func (u *Updater) CheckForUpdates() (*UpdateInfo, error) {
	u.mu.Lock()
	u.progress = UpdateProgress{Stage: "checking", StartTime: time.Now()}
	u.mu.Unlock()
	u.notifyProgress()

	// Manifest URL oluştur
	manifestURL := fmt.Sprintf("%s/manifest.json?platform=%s&arch=%s&version=%s&channel=%s",
		u.updateURL, runtime.GOOS, runtime.GOARCH, u.currentVersion, u.updateChannel)

	// Manifest'i indir
	resp, err := http.Get(manifestURL)
	if err != nil {
		return nil, fmt.Errorf("manifest indirilemedi: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("manifest indirme hatası: %d", resp.StatusCode)
	}

	var manifest UpdateManifest
	if err := json.NewDecoder(resp.Body).Decode(&manifest); err != nil {
		return nil, fmt.Errorf("manifest parse hatası: %v", err)
	}

	// Mevcut platform için güncelleme bul
	var latestUpdate *UpdateInfo
	for _, update := range manifest.Updates {
		if update.Platform != runtime.GOOS || update.Arch != runtime.GOARCH {
			continue
		}
		if update.Channel != u.updateChannel {
			continue
		}
		if !isNewerVersion(update.Version, u.currentVersion) {
			continue
		}
		if latestUpdate == nil || isNewerVersion(update.Version, latestUpdate.Version) {
			updateCopy := update
			latestUpdate = &updateCopy
		}
	}

	u.mu.Lock()
	u.lastCheck = time.Now()
	u.lastUpdate = latestUpdate
	u.mu.Unlock()

	if latestUpdate != nil {
		log.Printf("Yeni güncelleme mevcut: %s -> %s", u.currentVersion, latestUpdate.Version)

		if u.onUpdateAvailable != nil {
			u.onUpdateAvailable(latestUpdate)
		}

		// Otomatik güncelleme
		if u.autoUpdate {
			go u.DownloadAndInstall(latestUpdate)
		}
	}

	return latestUpdate, nil
}

// GetLastUpdate - Son güncelleme bilgisi
func (u *Updater) GetLastUpdate() *UpdateInfo {
	u.mu.RLock()
	defer u.mu.RUnlock()
	return u.lastUpdate
}

// GetProgress - Güncelleme ilerleme durumu
func (u *Updater) GetProgress() UpdateProgress {
	u.mu.RLock()
	defer u.mu.RUnlock()
	return u.progress
}

// IsUpdating - Güncelleme devam ediyor mu
func (u *Updater) IsUpdating() bool {
	u.mu.RLock()
	defer u.mu.RUnlock()
	return u.isUpdating
}

// DownloadAndInstall - İndir ve kur
func (u *Updater) DownloadAndInstall(update *UpdateInfo) error {
	u.mu.Lock()
	if u.isUpdating {
		u.mu.Unlock()
		return fmt.Errorf("güncelleme zaten devam ediyor")
	}
	u.isUpdating = true
	u.mu.Unlock()

	defer func() {
		u.mu.Lock()
		u.isUpdating = false
		u.mu.Unlock()
	}()

	var err error

	// 1. İndir
	downloadedFile, err := u.download(update)
	if err != nil {
		u.notifyComplete(false, err)
		return err
	}

	// 2. Doğrula
	if err := u.verifyChecksum(downloadedFile, update.Checksum); err != nil {
		os.Remove(downloadedFile)
		u.notifyComplete(false, err)
		return err
	}

	// 3. Yedekle
	if u.backupEnabled {
		if err := u.backup(); err != nil {
			log.Printf("Yedekleme hatası: %v (devam ediliyor)", err)
		}
	}

	// 4. Çıkart
	extractPath, err := u.extract(downloadedFile)
	if err != nil {
		u.notifyComplete(false, err)
		return err
	}

	// 5. Kur
	if err := u.install(extractPath); err != nil {
		u.notifyComplete(false, err)
		return err
	}

	// Temizlik
	os.Remove(downloadedFile)
	os.RemoveAll(extractPath)

	log.Printf("Güncelleme tamamlandı: %s", update.Version)
	u.notifyComplete(true, nil)

	// Otomatik yeniden başlatma
	if u.autoRestart {
		u.Restart()
	}

	return nil
}

// download - Güncellemeyi indir
func (u *Updater) download(update *UpdateInfo) (string, error) {
	u.setProgress("downloading", 0, 20)

	// İndirme dosya adı
	fileName := fmt.Sprintf("fikoyt-ajan-%s-%s-%s.zip", update.Version, runtime.GOOS, runtime.GOARCH)
	filePath := filepath.Join(u.downloadPath, fileName)

	// HTTP isteği
	resp, err := http.Get(update.DownloadURL)
	if err != nil {
		return "", fmt.Errorf("indirme başlatılamadı: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("indirme hatası: %d", resp.StatusCode)
	}

	// Dosya oluştur
	file, err := os.Create(filePath)
	if err != nil {
		return "", fmt.Errorf("dosya oluşturulamadı: %v", err)
	}
	defer file.Close()

	// İlerleme ile kopyala
	totalSize := resp.ContentLength
	if totalSize <= 0 {
		totalSize = update.DownloadSize
	}

	var downloaded int64
	startTime := time.Now()
	buf := make([]byte, 32*1024) // 32 KB buffer

	for {
		n, err := resp.Body.Read(buf)
		if n > 0 {
			file.Write(buf[:n])
			downloaded += int64(n)

			// İlerleme hesapla
			progress := float64(downloaded) / float64(totalSize) * 100
			elapsed := time.Since(startTime).Seconds()
			speed := float64(downloaded) / elapsed
			eta := 0
			if speed > 0 {
				eta = int(float64(totalSize-downloaded) / speed)
			}

			u.mu.Lock()
			u.progress.StageProgress = progress
			u.progress.TotalProgress = 20 + progress*0.4 // %20-%60 arası
			u.progress.DownloadedBytes = downloaded
			u.progress.TotalBytes = totalSize
			u.progress.Speed = speed
			u.progress.ETA = eta
			u.mu.Unlock()
			u.notifyProgress()
		}

		if err == io.EOF {
			break
		}
		if err != nil {
			return "", fmt.Errorf("indirme hatası: %v", err)
		}
	}

	return filePath, nil
}

// verifyChecksum - Checksum doğrula
func (u *Updater) verifyChecksum(filePath, expectedHash string) error {
	u.setProgress("verifying", 0, 65)

	file, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer file.Close()

	hash := sha256.New()
	if _, err := io.Copy(hash, file); err != nil {
		return err
	}

	actualHash := hex.EncodeToString(hash.Sum(nil))
	if !strings.EqualFold(actualHash, expectedHash) {
		return fmt.Errorf("checksum eşleşmiyor: beklenen %s, alınan %s", expectedHash, actualHash)
	}

	log.Println("Checksum doğrulandı")
	return nil
}

// backup - Mevcut versiyonu yedekle
func (u *Updater) backup() error {
	u.setProgress("backing_up", 0, 70)

	execPath, err := os.Executable()
	if err != nil {
		return err
	}

	backupName := fmt.Sprintf("fikoyt-ajan-%s-%s.bak", u.currentVersion, time.Now().Format("20060102150405"))
	backupPath := filepath.Join(u.backupPath, backupName)

	// Dosyayı kopyala
	src, err := os.Open(execPath)
	if err != nil {
		return err
	}
	defer src.Close()

	dst, err := os.Create(backupPath)
	if err != nil {
		return err
	}
	defer dst.Close()

	_, err = io.Copy(dst, src)
	if err != nil {
		return err
	}

	log.Printf("Yedekleme tamamlandı: %s", backupPath)
	return nil
}

// extract - ZIP dosyasını çıkart
func (u *Updater) extract(zipPath string) (string, error) {
	u.setProgress("extracting", 0, 75)

	extractPath := filepath.Join(u.downloadPath, "extract_"+time.Now().Format("20060102150405"))
	os.MkdirAll(extractPath, 0755)

	reader, err := zip.OpenReader(zipPath)
	if err != nil {
		return "", err
	}
	defer reader.Close()

	totalFiles := len(reader.File)
	for i, file := range reader.File {
		path := filepath.Join(extractPath, file.Name)

		// Zip slip koruması
		if !strings.HasPrefix(path, filepath.Clean(extractPath)+string(os.PathSeparator)) {
			continue
		}

		if file.FileInfo().IsDir() {
			os.MkdirAll(path, file.Mode())
			continue
		}

		os.MkdirAll(filepath.Dir(path), 0755)

		dstFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
		if err != nil {
			return "", err
		}

		srcFile, err := file.Open()
		if err != nil {
			dstFile.Close()
			return "", err
		}

		_, err = io.Copy(dstFile, srcFile)
		srcFile.Close()
		dstFile.Close()

		if err != nil {
			return "", err
		}

		// İlerleme
		progress := float64(i+1) / float64(totalFiles) * 100
		u.mu.Lock()
		u.progress.StageProgress = progress
		u.progress.TotalProgress = 75 + progress*0.15 // %75-%90 arası
		u.mu.Unlock()
		u.notifyProgress()
	}

	log.Println("Çıkartma tamamlandı")
	return extractPath, nil
}

// install - Güncellemeyi kur
func (u *Updater) install(extractPath string) error {
	u.setProgress("installing", 0, 90)

	execPath, err := os.Executable()
	if err != nil {
		return err
	}
	execDir := filepath.Dir(execPath)
	execName := filepath.Base(execPath)

	// Platform spesifik executable adı
	newExecName := execName
	if runtime.GOOS == "windows" && !strings.HasSuffix(execName, ".exe") {
		newExecName += ".exe"
	}

	newExecPath := filepath.Join(extractPath, newExecName)
	if _, err := os.Stat(newExecPath); os.IsNotExist(err) {
		// Alt dizinde ara
		entries, _ := os.ReadDir(extractPath)
		for _, entry := range entries {
			if entry.IsDir() {
				subPath := filepath.Join(extractPath, entry.Name(), newExecName)
				if _, err := os.Stat(subPath); err == nil {
					newExecPath = subPath
					break
				}
			}
		}
	}

	if _, err := os.Stat(newExecPath); os.IsNotExist(err) {
		return fmt.Errorf("yeni executable bulunamadı: %s", newExecName)
	}

	// Windows'ta çalışan dosyayı değiştirmek için rename kullan
	if runtime.GOOS == "windows" {
		oldPath := execPath + ".old"
		os.Remove(oldPath) // Varsa eski .old'u sil

		if err := os.Rename(execPath, oldPath); err != nil {
			return fmt.Errorf("eski dosya yeniden adlandırılamadı: %v", err)
		}
	}

	// Yeni dosyayı kopyala
	src, err := os.Open(newExecPath)
	if err != nil {
		return err
	}
	defer src.Close()

	dst, err := os.Create(filepath.Join(execDir, execName))
	if err != nil {
		return err
	}
	defer dst.Close()

	if _, err := io.Copy(dst, src); err != nil {
		return err
	}

	// Çalıştırılabilir yap (Unix)
	if runtime.GOOS != "windows" {
		os.Chmod(filepath.Join(execDir, execName), 0755)
	}

	u.setProgress("completed", 100, 100)
	log.Println("Kurulum tamamlandı")
	return nil
}

// Restart - Uygulamayı yeniden başlat
func (u *Updater) Restart() error {
	u.setProgress("restarting", 0, 100)

	execPath, err := os.Executable()
	if err != nil {
		return err
	}

	// Yeni process başlat
	cmd := exec.Command(execPath, os.Args[1:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin

	if err := cmd.Start(); err != nil {
		return err
	}

	log.Println("Yeniden başlatılıyor...")
	os.Exit(0)
	return nil
}

// Rollback - Yedekten geri yükle
func (u *Updater) Rollback() error {
	// En son yedeği bul
	entries, err := os.ReadDir(u.backupPath)
	if err != nil {
		return err
	}

	var latestBackup string
	var latestTime time.Time

	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".bak") {
			continue
		}

		info, err := entry.Info()
		if err != nil {
			continue
		}

		if info.ModTime().After(latestTime) {
			latestTime = info.ModTime()
			latestBackup = filepath.Join(u.backupPath, entry.Name())
		}
	}

	if latestBackup == "" {
		return fmt.Errorf("yedek bulunamadı")
	}

	execPath, err := os.Executable()
	if err != nil {
		return err
	}

	// Yedeği geri yükle
	src, err := os.Open(latestBackup)
	if err != nil {
		return err
	}
	defer src.Close()

	// Windows'ta rename kullan
	if runtime.GOOS == "windows" {
		tempPath := execPath + ".tmp"
		os.Rename(execPath, tempPath)
	}

	dst, err := os.Create(execPath)
	if err != nil {
		return err
	}
	defer dst.Close()

	if _, err := io.Copy(dst, src); err != nil {
		return err
	}

	log.Printf("Geri yükleme tamamlandı: %s", latestBackup)
	return nil
}

// CleanupBackups - Eski yedekleri temizle
func (u *Updater) CleanupBackups(keepCount int) error {
	entries, err := os.ReadDir(u.backupPath)
	if err != nil {
		return err
	}

	type backupFile struct {
		path    string
		modTime time.Time
	}

	var backups []backupFile
	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".bak") {
			continue
		}

		info, err := entry.Info()
		if err != nil {
			continue
		}

		backups = append(backups, backupFile{
			path:    filepath.Join(u.backupPath, entry.Name()),
			modTime: info.ModTime(),
		})
	}

	// Tarihe göre sırala (yeniden eskiye)
	for i := 0; i < len(backups)-1; i++ {
		for j := i + 1; j < len(backups); j++ {
			if backups[j].modTime.After(backups[i].modTime) {
				backups[i], backups[j] = backups[j], backups[i]
			}
		}
	}

	// Fazla yedekleri sil
	for i := keepCount; i < len(backups); i++ {
		os.Remove(backups[i].path)
		log.Printf("Eski yedek silindi: %s", backups[i].path)
	}

	return nil
}

// setProgress - İlerleme durumunu ayarla
func (u *Updater) setProgress(stage string, stageProgress, totalProgress float64) {
	u.mu.Lock()
	u.progress.Stage = stage
	u.progress.StageProgress = stageProgress
	u.progress.TotalProgress = totalProgress
	u.mu.Unlock()
	u.notifyProgress()
}

// notifyProgress - İlerleme callback'i çağır
func (u *Updater) notifyProgress() {
	if u.onUpdateProgress != nil {
		u.mu.RLock()
		progress := u.progress
		u.mu.RUnlock()
		u.onUpdateProgress(progress)
	}
}

// notifyComplete - Tamamlanma callback'i çağır
func (u *Updater) notifyComplete(success bool, err error) {
	if u.onUpdateComplete != nil {
		u.onUpdateComplete(success, err)
	}
}

// isNewerVersion - Versiyon karşılaştırma
func isNewerVersion(new, current string) bool {
	// Basit versiyon karşılaştırma (semver)
	newParts := parseVersion(new)
	currentParts := parseVersion(current)

	for i := 0; i < len(newParts) && i < len(currentParts); i++ {
		if newParts[i] > currentParts[i] {
			return true
		}
		if newParts[i] < currentParts[i] {
			return false
		}
	}

	return len(newParts) > len(currentParts)
}

// parseVersion - Versiyon parse
func parseVersion(version string) []int {
	// "v" prefix'ini kaldır
	version = strings.TrimPrefix(version, "v")

	// "-" sonrasını kaldır (pre-release)
	if idx := strings.Index(version, "-"); idx >= 0 {
		version = version[:idx]
	}

	parts := strings.Split(version, ".")
	result := make([]int, len(parts))

	for i, part := range parts {
		var num int
		fmt.Sscanf(part, "%d", &num)
		result[i] = num
	}

	return result
}

// GetCurrentVersion - Mevcut versiyon
func (u *Updater) GetCurrentVersion() string {
	return u.currentVersion
}

// GetLastCheckTime - Son kontrol zamanı
func (u *Updater) GetLastCheckTime() time.Time {
	u.mu.RLock()
	defer u.mu.RUnlock()
	return u.lastCheck
}

// GetUpdateStatus - Güncelleme durumu
func (u *Updater) GetUpdateStatus() map[string]interface{} {
	u.mu.RLock()
	defer u.mu.RUnlock()

	status := map[string]interface{}{
		"currentVersion": u.currentVersion,
		"updateChannel":  u.updateChannel,
		"lastCheck":      u.lastCheck,
		"isUpdating":     u.isUpdating,
		"autoUpdate":     u.autoUpdate,
		"autoRestart":    u.autoRestart,
	}

	if u.lastUpdate != nil {
		status["availableUpdate"] = map[string]interface{}{
			"version":     u.lastUpdate.Version,
			"releaseDate": u.lastUpdate.ReleaseDate,
			"critical":    u.lastUpdate.Critical,
			"size":        u.lastUpdate.DownloadSize,
		}
	}

	if u.isUpdating {
		status["progress"] = u.progress
	}

	return status
}
