// Package main provides the core EMLy application.
// EMLy is a desktop email viewer for .eml and .msg files built with Wails v2.
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"

	"emly/backend/utils"

	"github.com/wailsapp/wails/v2/pkg/runtime"
)

// =============================================================================
// App - Core Application Structure
// =============================================================================

// App is the main application struct that holds the application state and
// provides methods that are exposed to the frontend via Wails bindings.
//
// The struct manages:
//   - Application context for Wails runtime calls
//   - File paths for startup and currently loaded emails
//   - Tracking of open viewer windows to prevent duplicates
type App struct {
	// ctx is the Wails application context, used for runtime calls like dialogs
	ctx context.Context

	// StartupFilePath is set when the app is launched with an email file argument
	StartupFilePath string

	// CurrentMailFilePath tracks the currently loaded mail file path
	// Used for bug reports to include the relevant email file
	CurrentMailFilePath string

	// openImages tracks which images are currently open in viewer windows
	// The key is the filename, preventing duplicate viewers for the same file
	openImagesMux sync.Mutex
	openImages    map[string]bool

	// openPDFs tracks which PDFs are currently open in viewer windows
	openPDFsMux sync.Mutex
	openPDFs    map[string]bool

	// openEMLs tracks which EML attachments are currently open in viewer windows
	openEMLsMux sync.Mutex
	openEMLs    map[string]bool
}

// =============================================================================
// Constructor & Lifecycle
// =============================================================================

// NewApp creates and initializes a new App instance.
// This is called from main.go before the Wails application starts.
func NewApp() *App {
	return &App{
		openImages: make(map[string]bool),
		openPDFs:   make(map[string]bool),
		openEMLs:   make(map[string]bool),
	}
}

// startup is called by Wails when the application starts.
// It receives the application context which is required for Wails runtime calls.
//
// This method:
//   - Saves the context for later use
//   - Syncs CurrentMailFilePath with StartupFilePath if a file was opened via CLI
//   - Logs the startup mode (main app vs viewer window)
func (a *App) startup(ctx context.Context) {
	a.ctx = ctx

	// Sync CurrentMailFilePath with StartupFilePath if a file was opened via command line
	if a.StartupFilePath != "" {
		a.CurrentMailFilePath = a.StartupFilePath
	}

	// Check if this instance is running as a viewer (image/PDF) rather than main app
	isViewer := false
	for _, arg := range os.Args {
		if strings.Contains(arg, "--view-image") || strings.Contains(arg, "--view-pdf") {
			isViewer = true
			break
		}
	}

	if isViewer {
		Log("Viewer instance started")
	} else {
		Log("EMLy main application started")

		// Automatic update check on startup (if enabled)
		go func() {
			// Wait 5 seconds after startup to avoid blocking the UI
			time.Sleep(5 * time.Second)

			config := a.GetConfig()
			if config == nil {
				log.Printf("Failed to load config for auto-update check")
				return
			}

			// Check if auto-update is enabled
			if config.EMLy.UpdateAutoCheck == "true" && config.EMLy.UpdateCheckEnabled == "true" {
				log.Println("Performing automatic update check...")
				status, err := a.CheckForUpdates()
				if err != nil {
					log.Printf("Auto-update check failed: %v", err)
					return
				}

				// Emit event if update is available
				if status.UpdateAvailable {
					log.Printf("Update available: %s -> %s", status.CurrentVersion, status.AvailableVersion)
					runtime.EventsEmit(ctx, "update:available", status)
				} else {
					log.Println("No updates available")
				}
			}
		}()
	}
}

// shutdown is called by Wails when the application is closing.
// Used for cleanup operations.
func (a *App) shutdown(ctx context.Context) {
	// Best-effort cleanup - currently no resources require explicit cleanup
}

// QuitApp terminates the application.
// It first calls Wails Quit to properly close the window,
// then forces an exit with a specific code.
func (a *App) QuitApp() {
	runtime.Quit(a.ctx)
	// Exit with code 133 (133 + 5 = 138, SIGTRAP-like exit)
	os.Exit(133)
}

// RestartApp performs a full application restart, including the Go backend.
// It schedules a new process via PowerShell with a short delay to ensure the
// single-instance lock is released before the new instance starts, then exits.
func (a *App) RestartApp() error {
	exe, err := os.Executable()
	if err != nil {
		Log("RestartApp: failed to get executable path:", err)
		return err
	}

	// Escape single quotes in the path for PowerShell string literal
	safePath := strings.ReplaceAll(exe, "'", "''")
	script := fmt.Sprintf(`Start-Sleep -Seconds 1; Start-Process '%s'`, safePath)

	cmd := exec.Command("powershell", "-WindowStyle", "Hidden", "-Command", script)
	if err := cmd.Start(); err != nil {
		Log("RestartApp: failed to schedule restart:", err)
		return err
	}

	Log("RestartApp: scheduled restart, quitting current instance...")
	runtime.Quit(a.ctx)
	return nil
}

// =============================================================================
// Configuration Management
// =============================================================================

// GetConfig loads and returns the application configuration from config.ini.
// Returns nil if the configuration cannot be loaded.
func (a *App) GetConfig() *utils.Config {
	cfgPath := utils.DefaultConfigPath()
	cfg, err := utils.LoadConfig(cfgPath)
	if err != nil {
		Log("Failed to load config:", err)
		return nil
	}
	return cfg
}

// SaveConfig persists the provided configuration to config.ini.
// Returns an error if saving fails.
func (a *App) SaveConfig(cfg *utils.Config) error {
	cfgPath := utils.DefaultConfigPath()
	if err := utils.SaveConfig(cfgPath, cfg); err != nil {
		Log("Failed to save config:", err)
		return err
	}
	return nil
}

// =============================================================================
// Startup File Management
// =============================================================================

// GetStartupFile returns the file path if the app was launched with an email file argument.
// Returns an empty string if no file was specified at startup.
func (a *App) GetStartupFile() string {
	return a.StartupFilePath
}

// SetCurrentMailFilePath updates the path of the currently loaded mail file.
// This is called when the user opens a file via the file dialog.
func (a *App) SetCurrentMailFilePath(filePath string) {
	a.CurrentMailFilePath = filePath
}

// GetCurrentMailFilePath returns the path of the currently loaded mail file.
// Used by bug reports to include the relevant email file.
func (a *App) GetCurrentMailFilePath() string {
	return a.CurrentMailFilePath
}

// =============================================================================
// System Information
// =============================================================================

// GetMachineData retrieves system information about the current machine.
// Returns hostname, OS version, hardware ID, etc.
func (a *App) GetMachineData() *utils.MachineInfo {
	data, _ := utils.GetMachineInfo()
	return data
}

// IsDebuggerRunning checks if a debugger is attached to the application.
// Used for anti-debugging protection in production builds.
func (a *App) IsDebuggerRunning() bool {
	if a == nil {
		return false
	}
	return utils.IsDebugged()
}
