package main

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

const (
	// Aktuelle Version der App
	CurrentVersion = "0.4.5"
	// Update-Check URL
	UpdateURL = "https://speiseplan.supertoll.xyz/version.json"
)

// Updater verwaltet App-Updates
type Updater struct {
	CurrentVersion string
	UpdateURL      string
	HTTPClient     *http.Client
}

// NewUpdater erstellt einen neuen Updater
func NewUpdater() *Updater {
	return &Updater{
		CurrentVersion: CurrentVersion,
		UpdateURL:      UpdateURL,
		HTTPClient: &http.Client{
			Timeout: 10 * time.Second,
		},
	}
}

// VersionResponse repräsentiert die Antwort vom Update-Server
type VersionResponse struct {
	Version      string `json:"version"`
	DownloadURL  string `json:"download_url"`
	ReleaseNotes string `json:"release_notes"`
	Checksum     string `json:"checksum"`
}

// CheckForUpdate prüft ob ein Update verfügbar ist
func (u *Updater) CheckForUpdate() (*UpdateInfo, error) {
	resp, err := u.HTTPClient.Get(u.UpdateURL)
	if err != nil {
		return nil, fmt.Errorf("failed to check for updates: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("update server returned status %d", resp.StatusCode)
	}

	var versionResp VersionResponse
	if err := json.NewDecoder(resp.Body).Decode(&versionResp); err != nil {
		return nil, fmt.Errorf("failed to parse version response: %w", err)
	}

	// Versions-Vergleich (vereinfacht)
	available := compareVersions(u.CurrentVersion, versionResp.Version) < 0

	return &UpdateInfo{
		Available:      available,
		CurrentVersion: u.CurrentVersion,
		LatestVersion:  versionResp.Version,
		DownloadURL:    versionResp.DownloadURL,
		ReleaseNotes:   versionResp.ReleaseNotes,
		Checksum:       versionResp.Checksum,
	}, nil
}

// DownloadUpdate lädt ein verfügbares Update herunter und verifiziert die Checksum
func (u *Updater) DownloadUpdate(info UpdateInfo) (string, error) {
	if info.DownloadURL == "" {
		return "", fmt.Errorf("keine Download-URL vorhanden")
	}

	// Nur HTTPS erlauben
	if !strings.HasPrefix(info.DownloadURL, "https://") {
		return "", fmt.Errorf("nur HTTPS-Downloads sind erlaubt, URL: %s", info.DownloadURL)
	}

	// Temporäres Verzeichnis
	tmpDir, err := os.MkdirTemp("", "speiseplan-update-*")
	if err != nil {
		return "", fmt.Errorf("temporäres Verzeichnis konnte nicht erstellt werden: %w", err)
	}

	// Dateiname aus URL
	parts := strings.Split(info.DownloadURL, "/")
	filename := parts[len(parts)-1]
	if filename == "" {
		filename = "speiseplan-update.exe"
	}
	destPath := filepath.Join(tmpDir, filename)

	// Download
	resp, err := u.HTTPClient.Get(info.DownloadURL)
	if err != nil {
		return "", fmt.Errorf("Download fehlgeschlagen: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("Download-Server antwortete mit Status %d", resp.StatusCode)
	}

	outFile, err := os.Create(destPath)
	if err != nil {
		return "", fmt.Errorf("Datei konnte nicht erstellt werden: %w", err)
	}
	defer outFile.Close()

	// Gleichzeitig schreiben und Hash berechnen
	hasher := sha256.New()
	writer := io.MultiWriter(outFile, hasher)

	if _, err := io.Copy(writer, resp.Body); err != nil {
		return "", fmt.Errorf("Download-Fehler: %w", err)
	}
	outFile.Close()

	// SHA256 verifizieren
	if info.Checksum != "" {
		actualHash := hex.EncodeToString(hasher.Sum(nil))
		expectedHash := strings.ToLower(strings.TrimSpace(info.Checksum))
		if actualHash != expectedHash {
			os.Remove(destPath)
			return "", fmt.Errorf("Checksum-Fehler: erwartet %s, erhalten %s", expectedHash, actualHash)
		}
	}

	msg := fmt.Sprintf("Update v%s wurde nach %s heruntergeladen. Bitte die App beenden und die neue Version starten.", info.LatestVersion, destPath)
	return msg, nil
}

// compareVersions vergleicht zwei Versionsstrings
// Gibt -1 zurück wenn v1 < v2, 0 wenn v1 == v2, 1 wenn v1 > v2
func compareVersions(v1, v2 string) int {
	// Semantic Versioning Vergleich mit numerischem Parsing
	v1Parts := strings.Split(strings.TrimPrefix(v1, "v"), ".")
	v2Parts := strings.Split(strings.TrimPrefix(v2, "v"), ".")

	// Normalisiere auf gleiche Länge
	for len(v1Parts) < 3 {
		v1Parts = append(v1Parts, "0")
	}
	for len(v2Parts) < 3 {
		v2Parts = append(v2Parts, "0")
	}

	for i := 0; i < 3; i++ {
		n1, err1 := strconv.Atoi(v1Parts[i])
		n2, err2 := strconv.Atoi(v2Parts[i])
		if err1 != nil || err2 != nil {
			// Fallback auf String-Vergleich
			if v1Parts[i] < v2Parts[i] {
				return -1
			}
			if v1Parts[i] > v2Parts[i] {
				return 1
			}
			continue
		}
		if n1 < n2 {
			return -1
		}
		if n1 > n2 {
			return 1
		}
	}

	return 0
}