package app

import (
	"database/sql"
	"encoding/hex"
	"fmt"
	"log/slog"
	"os"
	"path/filepath"

	"github.com/vstockwell/wraith/internal/ai"
	"github.com/vstockwell/wraith/internal/connections"
	"github.com/vstockwell/wraith/internal/credentials"
	"github.com/vstockwell/wraith/internal/db"
	"github.com/vstockwell/wraith/internal/plugin"
	"github.com/vstockwell/wraith/internal/rdp"
	"github.com/vstockwell/wraith/internal/session"
	"github.com/vstockwell/wraith/internal/settings"
	"github.com/vstockwell/wraith/internal/sftp"
	"github.com/vstockwell/wraith/internal/ssh"
	"github.com/vstockwell/wraith/internal/theme"
	"github.com/vstockwell/wraith/internal/updater"
	"github.com/vstockwell/wraith/internal/vault"
)

// WraithApp is the main application struct that wires together all services
// and exposes vault management methods to the frontend via Wails bindings.
type WraithApp struct {
	db          *sql.DB
	Vault       *vault.VaultService
	Settings    *settings.SettingsService
	Connections *connections.ConnectionService
	Themes      *theme.ThemeService
	Sessions    *session.Manager
	Plugins     *plugin.Registry
	SSH         *ssh.SSHService
	SFTP        *sftp.SFTPService
	RDP         *rdp.RDPService
	Credentials *credentials.CredentialService
	AI          *ai.AIService
	Updater     *updater.UpdateService
	oauthMgr    *ai.OAuthManager
	unlocked    bool
}

// New creates and initializes the WraithApp, opening the database, running
// migrations, creating all services, and seeding built-in themes.
// The version string is the build-time semver (e.g. "0.2.0") used by the
// auto-updater to check for newer releases.
func New(version string) (*WraithApp, error) {
	dataDir := dataDirectory()
	dbPath := filepath.Join(dataDir, "wraith.db")

	slog.Info("opening database", "path", dbPath)
	database, err := db.Open(dbPath)
	if err != nil {
		return nil, fmt.Errorf("open database: %w", err)
	}

	if err := db.Migrate(database); err != nil {
		return nil, fmt.Errorf("run migrations: %w", err)
	}

	settingsSvc := settings.NewSettingsService(database)
	connSvc := connections.NewConnectionService(database)
	themeSvc := theme.NewThemeService(database)
	sessionMgr := session.NewManager()
	pluginReg := plugin.NewRegistry()

	// No-op output handler — Wails event emission will be wired at runtime.
	sshSvc := ssh.NewSSHService(database, func(sessionID string, data []byte) {
		// TODO: Emit Wails event "ssh:output" with sessionID + data
		_ = sessionID
		_ = data
	})
	sftpSvc := sftp.NewSFTPService()

	// RDP service with platform-aware backend factory.
	// On Windows the factory returns a FreeRDPBackend backed by libfreerdp3.dll;
	// on other platforms it falls back to MockBackend for development.
	rdpSvc := rdp.NewRDPService(func() rdp.RDPBackend {
		return rdp.NewProductionBackend()
	})

	// CredentialService requires the vault to be unlocked, so it starts nil.
	// It is created lazily after the vault is unlocked via initCredentials().

	// AI copilot services — OAuthManager starts without a vault reference;
	// it will be wired once the vault is unlocked.
	oauthMgr := ai.NewOAuthManager(settingsSvc, nil)
	toolRouter := ai.NewToolRouter()
	toolRouter.SetServices(sshSvc, sftpSvc, rdpSvc, sessionMgr, connSvc)
	convMgr := ai.NewConversationManager(database)
	aiSvc := ai.NewAIService(oauthMgr, toolRouter, convMgr)
	toolRouter.SetAIService(aiSvc)

	// Seed built-in themes on every startup (INSERT OR IGNORE keeps it idempotent)
	if err := themeSvc.SeedBuiltins(); err != nil {
		slog.Warn("failed to seed themes", "error", err)
	}

	updaterSvc := updater.NewUpdateService(version)

	return &WraithApp{
		db:          database,
		Settings:    settingsSvc,
		Connections: connSvc,
		Themes:      themeSvc,
		Sessions:    sessionMgr,
		Plugins:     pluginReg,
		SSH:         sshSvc,
		SFTP:        sftpSvc,
		RDP:         rdpSvc,
		AI:          aiSvc,
		Updater:     updaterSvc,
		oauthMgr:    oauthMgr,
	}, nil
}

// dataDirectory returns the path where Wraith stores its data.
// On Windows with APPDATA set, it uses %APPDATA%\Wraith.
// On macOS/Linux with XDG_DATA_HOME or HOME, it uses the appropriate path.
// Falls back to the current working directory for development.
func dataDirectory() string {
	// Windows
	if appData := os.Getenv("APPDATA"); appData != "" {
		return filepath.Join(appData, "Wraith")
	}

	// macOS / Linux: use XDG_DATA_HOME or fallback to ~/.local/share
	if home, err := os.UserHomeDir(); err == nil {
		if xdg := os.Getenv("XDG_DATA_HOME"); xdg != "" {
			return filepath.Join(xdg, "wraith")
		}
		return filepath.Join(home, ".local", "share", "wraith")
	}

	// Dev fallback
	return "."
}

// IsFirstRun checks whether the vault has been set up by looking for vault_salt in settings.
func (a *WraithApp) IsFirstRun() bool {
	salt, _ := a.Settings.Get("vault_salt")
	return salt == ""
}

// CreateVault sets up the vault with a master password. It generates a salt,
// derives an encryption key, and stores the salt and a check value in settings.
func (a *WraithApp) CreateVault(password string) error {
	salt, err := vault.GenerateSalt()
	if err != nil {
		return fmt.Errorf("generate salt: %w", err)
	}

	key := vault.DeriveKey(password, salt)
	a.Vault = vault.NewVaultService(key)

	// Store salt as hex in settings
	if err := a.Settings.Set("vault_salt", hex.EncodeToString(salt)); err != nil {
		return fmt.Errorf("store salt: %w", err)
	}

	// Encrypt a known check value — used to verify the password on unlock
	check, err := a.Vault.Encrypt("wraith-vault-check")
	if err != nil {
		return fmt.Errorf("encrypt check value: %w", err)
	}
	if err := a.Settings.Set("vault_check", check); err != nil {
		return fmt.Errorf("store check value: %w", err)
	}

	a.unlocked = true
	a.initCredentials()
	slog.Info("vault created successfully")
	return nil
}

// Unlock verifies the master password against the stored check value and
// initializes the vault service for decryption.
func (a *WraithApp) Unlock(password string) error {
	saltHex, err := a.Settings.Get("vault_salt")
	if err != nil || saltHex == "" {
		return fmt.Errorf("vault not set up — call CreateVault first")
	}

	salt, err := hex.DecodeString(saltHex)
	if err != nil {
		return fmt.Errorf("decode salt: %w", err)
	}

	key := vault.DeriveKey(password, salt)
	vs := vault.NewVaultService(key)

	// Verify by decrypting the stored check value
	checkEncrypted, err := a.Settings.Get("vault_check")
	if err != nil || checkEncrypted == "" {
		return fmt.Errorf("vault check value missing")
	}

	checkPlain, err := vs.Decrypt(checkEncrypted)
	if err != nil {
		return fmt.Errorf("incorrect master password")
	}
	if checkPlain != "wraith-vault-check" {
		return fmt.Errorf("incorrect master password")
	}

	a.Vault = vs
	a.unlocked = true
	a.initCredentials()
	slog.Info("vault unlocked successfully")
	return nil
}

// IsUnlocked returns whether the vault is currently unlocked.
func (a *WraithApp) IsUnlocked() bool {
	return a.unlocked
}

// initCredentials creates the CredentialService after the vault is unlocked
// and wires the vault to services that need it (OAuth, etc.).
func (a *WraithApp) initCredentials() {
	if a.Vault != nil {
		a.Credentials = credentials.NewCredentialService(a.db, a.Vault)
		if a.oauthMgr != nil {
			a.oauthMgr.SetVault(a.Vault)
		}
	}
}
