package steps

import (
	"bufio"
	"fmt"
	"miasma-installer/config"
	"miasma-installer/tui/styles"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
	"time"

	tea "github.com/charmbracelet/bubbletea"
)

type InstallModel struct {
	config       *config.InstallConfig
	Finished     bool
	status       string
	progress     []string
	err          error
	currentStep  int
	totalSteps   int
}

func NewInstallModel(disk string, enableLUKS bool, hostname string, username string, userPassword string, rootPassword string) InstallModel {
	return InstallModel{
		config: &config.InstallConfig{
			Disk:         disk,
			EnableLUKS:   enableLUKS,
			Hostname:     hostname,
			Username:     username,
			UserPassword: userPassword,
			RootPassword: rootPassword,
			Timezone:     "UTC",
			Locale:       "en_US.UTF-8",
		},
		status:      "Preparing installation...",
		progress:    []string{},
		currentStep: 0,
		totalSteps:  8,
	}
}

func (m InstallModel) Init() tea.Cmd {
	return m.startInstall()
}

type installCompleteMsg struct {
	err error
}

type installProgressMsg struct {
	step    int
	message string
}

func (m InstallModel) startInstall() tea.Cmd {
	return func() tea.Msg {
		// Save archinstall configuration files
		if err := m.saveConfigurationFiles(); err != nil {
			return installCompleteMsg{err: fmt.Errorf("failed to save configuration files: %w", err)}
		}
		
		if err := m.performInstall(); err != nil {
			return installCompleteMsg{err: err}
		}
		return installCompleteMsg{err: nil}
	}
}

func (m InstallModel) performInstall() error {
	m.updateProgress(1, "Partitioning disk...")
	if err := m.partitionDisk(); err != nil {
		return fmt.Errorf("partitioning failed: %w", err)
	}

	m.updateProgress(2, "Formatting partitions...")
	if err := m.formatPartitions(); err != nil {
		return fmt.Errorf("formatting failed: %w", err)
	}

	m.updateProgress(3, "Mounting filesystems...")
	if err := m.mountFilesystems(); err != nil {
		return fmt.Errorf("mounting failed: %w", err)
	}

	m.updateProgress(4, "Installing base system...")
	if err := m.installBase(); err != nil {
		return fmt.Errorf("base install failed: %w", err)
	}

	m.updateProgress(5, "Configuring system...")
	if err := m.configureSystem(); err != nil {
		return fmt.Errorf("configuration failed: %w", err)
	}

	m.updateProgress(6, "Installing bootloader...")
	if err := m.installBootloader(); err != nil {
		return fmt.Errorf("bootloader install failed: %w", err)
	}

	m.updateProgress(7, "Installing desktop and packages...")
	if err := m.installDesktop(); err != nil {
		return fmt.Errorf("desktop install failed: %w", err)
	}

	m.updateProgress(8, "Running post-install scripts...")
	if err := m.runPostInstallScripts(); err != nil {
		return fmt.Errorf("post-install scripts failed: %w", err)
	}

	return nil
}

func (m InstallModel) updateProgress(step int, message string) {
	m.currentStep = step
	m.status = message
	time.Sleep(100 * time.Millisecond)
}

func (m InstallModel) saveConfigurationFiles() error {
	m.updateProgress(0, "Saving configuration files...")

	// Save user configuration
	if err := m.config.SaveArchInstallConfig("/tmp/user_configuration.json"); err != nil {
		return fmt.Errorf("failed to save user configuration: %w", err)
	}

	// Save user credentials
	if err := m.config.SaveUserCredentials("/tmp/user_credentials.json"); err != nil {
		return fmt.Errorf("failed to save user credentials: %w", err)
	}

	// Copy configuration files to target system
	os.MkdirAll("/mnt/var/log/archinstall", 0755)
	exec.Command("cp", "/tmp/user_configuration.json", "/mnt/var/log/archinstall/").Run()
	exec.Command("cp", "/tmp/user_credentials.json", "/mnt/var/log/archinstall/").Run()

	return nil
}

func (m InstallModel) partitionDisk() error {
	disk := m.config.Disk

	wipeDisk := exec.Command("wipefs", "-af", disk)
	if err := wipeDisk.Run(); err != nil {
		return err
	}

	sgdisk := exec.Command("sgdisk", "-Z", disk)
	if err := sgdisk.Run(); err != nil {
		return err
	}

	createEFI := exec.Command("sgdisk", "-n", "1:0:+512M", "-t", "1:ef00", disk)
	if err := createEFI.Run(); err != nil {
		return err
	}

	createRoot := exec.Command("sgdisk", "-n", "2:0:0", "-t", "2:8300", disk)
	if err := createRoot.Run(); err != nil {
		return err
	}

	time.Sleep(1 * time.Second)
	exec.Command("partprobe", disk).Run()

	return nil
}

func (m InstallModel) formatPartitions() error {
	disk := m.config.Disk
	efiPart := disk + "1"
	rootPart := disk + "2"

	if strings.Contains(disk, "nvme") || strings.Contains(disk, "mmcblk") {
		efiPart = disk + "p1"
		rootPart = disk + "p2"
	}

	mkfsEFI := exec.Command("mkfs.fat", "-F32", efiPart)
	if err := mkfsEFI.Run(); err != nil {
		return err
	}

	if m.config.EnableLUKS {
		cryptsetup := exec.Command("cryptsetup", "luksFormat", "--type", "luks2", rootPart)
		cryptsetup.Stdin = strings.NewReader(m.config.RootPassword + "\n")
		if err := cryptsetup.Run(); err != nil {
			return err
		}

		cryptOpen := exec.Command("cryptsetup", "open", rootPart, "cryptroot")
		cryptOpen.Stdin = strings.NewReader(m.config.RootPassword + "\n")
		if err := cryptOpen.Run(); err != nil {
			return err
		}

		rootPart = "/dev/mapper/cryptroot"
	}

	mkfsBtrfs := exec.Command("mkfs.btrfs", "-f", rootPart)
	if err := mkfsBtrfs.Run(); err != nil {
		return err
	}

	return nil
}

func (m InstallModel) mountFilesystems() error {
	disk := m.config.Disk
	rootPart := disk + "2"
	efiPart := disk + "1"

	if strings.Contains(disk, "nvme") || strings.Contains(disk, "mmcblk") {
		efiPart = disk + "p1"
		rootPart = disk + "p2"
	}

	if m.config.EnableLUKS {
		rootPart = "/dev/mapper/cryptroot"
	}

	os.MkdirAll("/mnt", 0755)
	mount := exec.Command("mount", rootPart, "/mnt")
	if err := mount.Run(); err != nil {
		return err
	}

	btrfsOpts := "subvol=@,compress=zstd,noatime"
	for _, subvol := range []string{"@", "@home", "@var", "@snapshots"} {
		create := exec.Command("btrfs", "subvolume", "create", "/mnt/"+subvol)
		create.Run()
	}

	exec.Command("umount", "/mnt").Run()

	mount = exec.Command("mount", "-o", btrfsOpts, rootPart, "/mnt")
	if err := mount.Run(); err != nil {
		return err
	}

	os.MkdirAll("/mnt/home", 0755)
	os.MkdirAll("/mnt/var", 0755)
	os.MkdirAll("/mnt/.snapshots", 0755)
	os.MkdirAll("/mnt/boot", 0755)

	mountHome := exec.Command("mount", "-o", "subvol=@home,compress=zstd,noatime", rootPart, "/mnt/home")
	mountHome.Run()

	mountVar := exec.Command("mount", "-o", "subvol=@var,compress=zstd,noatime", rootPart, "/mnt/var")
	mountVar.Run()

	mountSnap := exec.Command("mount", "-o", "subvol=@snapshots,compress=zstd,noatime", rootPart, "/mnt/.snapshots")
	mountSnap.Run()

	mountEFI := exec.Command("mount", efiPart, "/mnt/boot")
	if err := mountEFI.Run(); err != nil {
		return err
	}

	return nil
}

func (m InstallModel) installBase() error {
	// Base Miasma OS packages with security hardening
	packages := []string{
		"base",
		"base-devel",
		"linux-hardened",
		"linux-firmware",
		"btrfs-progs",
		"neovim", // Default text editor as specified in product description
		"zsh",    // Default shell as specified in product description
		"alacritty", // Default terminal as specified in product description
		"tmux",
		"git",
		"networkmanager",
		"opendoas", // Replace sudo with doas as specified in product description
		"firejail",
		"apparmor",
		"nftables", // Using nftables instead of ufw as requested
		"hardened-malloc", // Hardened malloc by GrapheneOS
	}

	pacstrap := exec.Command("pacstrap", append([]string{"/mnt"}, packages...)...)
	pacstrap.Stdout = os.Stdout
	pacstrap.Stderr = os.Stderr
	if err := pacstrap.Run(); err != nil {
		return err
	}

	return nil
}

func (m InstallModel) configureSystem() error {
	genfstab := exec.Command("genfstab", "-U", "/mnt")
	fstab, err := genfstab.Output()
	if err != nil {
		return fmt.Errorf("genfstab failed: %w", err)
	}
	os.WriteFile("/mnt/etc/fstab", fstab, 0644)

	os.WriteFile("/mnt/etc/hostname", []byte(m.config.Hostname+"\n"), 0644)

	hosts := fmt.Sprintf("127.0.0.1\tlocalhost\n::1\t\tlocalhost\n127.0.1.1\t%s.localdomain\t%s\n",
		m.config.Hostname, m.config.Hostname)
	os.WriteFile("/mnt/etc/hosts", []byte(hosts), 0644)

	os.WriteFile("/mnt/etc/locale.gen", []byte("en_US.UTF-8 UTF-8\n"), 0644)
	if _, err := chroot([]string{"locale-gen"}); err != nil {
		return fmt.Errorf("locale-gen failed: %w", err)
	}

	os.WriteFile("/mnt/etc/locale.conf", []byte("LANG=en_US.UTF-8\n"), 0644)

	chroot([]string{"ln", "-sf", "/usr/share/zoneinfo/UTC", "/etc/localtime"})
	chroot([]string{"hwclock", "--systohc"})

	// Create user with zsh as default shell
	useraddCmd := fmt.Sprintf("useradd -m -G wheel -s /bin/zsh %s", m.config.Username)
	if _, err := chroot([]string{"sh", "-c", useraddCmd}); err != nil {
		return fmt.Errorf("useradd failed: %w", err)
	}

	passwdCmd := fmt.Sprintf("echo '%s:%s' | chpasswd", m.config.Username, m.config.UserPassword)
	if _, err := chroot([]string{"sh", "-c", passwdCmd}); err != nil {
		return fmt.Errorf("user password set failed: %w", err)
	}

	// Set root password
	rootPasswd := m.config.RootPassword
	if rootPasswd == "" {
		rootPasswd = m.config.UserPassword
	}
	rootPasswdCmd := fmt.Sprintf("echo 'root:%s' | chpasswd", rootPasswd)
	if _, err := chroot([]string{"sh", "-c", rootPasswdCmd}); err != nil {
		return fmt.Errorf("root password set failed: %w", err)
	}

	// Configure doas instead of sudo
	doasConf := fmt.Sprintf("permit persist :wheel\npermit nopass root\n")
	os.WriteFile("/mnt/etc/doas.conf", []byte(doasConf), 0600)

	// Configure sudoers for compatibility (limited use)
	sudoers := "%wheel ALL=(ALL:ALL) ALL\n"
	os.WriteFile("/mnt/etc/sudoers.d/wheel", []byte(sudoers), 0440)

	// Enable required services
	services := []string{
		"NetworkManager",
		"ufw",
		"apparmor",
	}

	for _, service := range services {
		if _, err := chroot([]string{"systemctl", "enable", service}); err != nil {
			return fmt.Errorf("failed to enable %s: %w", service, err)
		}
	}

	// Apply system hardening
	if err := m.applySystemHardening(); err != nil {
		return fmt.Errorf("system hardening failed: %w", err)
	}

	// Configure default shell and applications
	if err := m.configureDefaults(); err != nil {
		return fmt.Errorf("default configuration failed: %w", err)
	}

	return nil
}

func (m InstallModel) applySystemHardening() error {
	// Configure kernel parameters for hardening
	sysctlConf := `# Miasma OS Security Hardening
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
kernel.unprivileged_bpf_disabled = 1
net.core.bpf_jit_harden = 2
kernel.yama.ptrace_scope = 2
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.icmp_echo_ignore_all = 1
`
	if err := os.WriteFile("/mnt/etc/sysctl.d/99-hardening.conf", []byte(sysctlConf), 0644); err != nil {
		return fmt.Errorf("failed to write sysctl config: %w", err)
	}

	// Configure nftables (replacing ufw)
	if _, err := chroot([]string{"pacman", "-Sy", "--noconfirm", "nftables"}); err != nil {
		return fmt.Errorf("failed to install nftables: %w", err)
	}

	// Set secure umask
	profileAppend := "\n# Secure umask\numask 077\n"
	f, err := os.OpenFile("/mnt/etc/profile", os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return fmt.Errorf("failed to open profile: %w", err)
	}
	defer f.Close()
	if _, err := f.WriteString(profileAppend); err != nil {
		return fmt.Errorf("failed to append to profile: %w", err)
	}

	// Disable core dumps
	limitsAppend := "\n# Disable core dumps\n* hard core 0\n"
	f2, err := os.OpenFile("/mnt/etc/security/limits.conf", os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return fmt.Errorf("failed to open limits.conf: %w", err)
	}
	defer f2.Close()
	if _, err := f2.WriteString(limitsAppend); err != nil {
		return fmt.Errorf("failed to append to limits.conf: %w", err)
	}

	return nil
}

func (m InstallModel) configureDefaults() error {
	// Install and configure Ungoogled Chromium with Wayland support
	chromiumPackages := []string{
		"ungoogled-chromium",
		"chromium-web-store", // For extension installation
	}

	for _, pkg := range chromiumPackages {
		if _, err := chroot([]string{"pacman", "-Sy", "--noconfirm", pkg}); err != nil {
			return fmt.Errorf("failed to install %s: %w", pkg, err)
		}
	}

	// Configure default applications for the user
	userHome := fmt.Sprintf("/home/%s", m.config.Username)
	chromiumDir := fmt.Sprintf("/mnt%s/.config/chromium", userHome)
	os.MkdirAll(chromiumDir, 0755)
	if err := os.WriteFile(fmt.Sprintf("%s/Local State", chromiumDir), []byte("{}"), 0644); err != nil {
		return fmt.Errorf("failed to create chromium config: %w", err)
	}

	// Set up LazyVim configuration for Neovim
	lazyVimConfig := `return {
  colorscheme = "kanagawa-dragon",
  defaults = {
    indent = 2,
    tabstop = 2,
    shiftwidth = 2,
  },
}
`
	lazyDir := fmt.Sprintf("/mnt%s/.config/nvim", userHome)
	os.MkdirAll(lazyDir, 0755)
	if err := os.WriteFile(fmt.Sprintf("%s/lazyvim.json", lazyDir), []byte(lazyVimConfig), 0644); err != nil {
		return fmt.Errorf("failed to create lazyvim config: %w", err)
	}

	// Set up Zsh configuration with Kanagawa theme
	zshrc := fmt.Sprintf(`# Miasma OS Zsh Configuration
export ZSH="/home/%s/.oh-my-zsh"
ZSH_THEME="kanagawa"
plugins=(git sudo extract)

source $ZSH/oh-my-zsh.sh

# Aliases
alias ls="ls --color=auto"
alias la="ls -la"
alias vi="nvim"
alias vim="nvim"

# Default applications
export EDITOR="nvim"
export TERMINAL="alacritty"
`, m.config.Username)

	zshrcPath := fmt.Sprintf("/mnt%s/.zshrc", userHome)
	if err := os.WriteFile(zshrcPath, []byte(zshrc), 0644); err != nil {
		return fmt.Errorf("failed to create zshrc: %w", err)
	}

	// Set proper ownership for user files
	chownCmd := fmt.Sprintf("chown -R %s:%s /home/%s", m.config.Username, m.config.Username, m.config.Username)
	if _, err := chroot([]string{"sh", "-c", chownCmd}); err != nil {
		return fmt.Errorf("failed to set user ownership: %w", err)
	}

	return nil
}

func (m InstallModel) installBootloader() error {
	if _, err := chroot([]string{"bootctl", "install"}); err != nil {
		return fmt.Errorf("bootctl install failed: %w", err)
	}

	loaderConf := `default arch.conf
timeout 3
console-mode max
editor no
`
	os.WriteFile("/mnt/boot/loader/loader.conf", []byte(loaderConf), 0644)

	disk := m.config.Disk
	rootPart := disk + "2"
	if strings.Contains(disk, "nvme") || strings.Contains(disk, "mmcblk") {
		rootPart = disk + "p2"
	}

	var options string
	if m.config.EnableLUKS {
		cryptUUID := ""
		blkid := exec.Command("blkid", "-s", "UUID", "-o", "value", rootPart)
		output, _ := blkid.Output()
		cryptUUID = strings.TrimSpace(string(output))
		options = fmt.Sprintf("cryptdevice=UUID=%s:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw quiet splash", cryptUUID)
	} else {
		blkid := exec.Command("blkid", "-s", "UUID", "-o", "value", rootPart)
		output, _ := blkid.Output()
		rootUUID := strings.TrimSpace(string(output))
		options = fmt.Sprintf("root=UUID=%s rootflags=subvol=@ rw quiet splash", rootUUID)
	}

	entryConf := fmt.Sprintf(`title   Miasma OS
linux   /vmlinuz-linux-hardened
initrd  /initramfs-linux-hardened.img
options %s
`, options)
	os.WriteFile("/mnt/boot/loader/entries/arch.conf", []byte(entryConf), 0644)

	if m.config.EnableLUKS {
		mkinitcpioConf, err := os.ReadFile("/mnt/etc/mkinitcpio.conf")
		if err != nil {
			return fmt.Errorf("failed to read mkinitcpio.conf: %w", err)
		}

		newConf := string(mkinitcpioConf)
		newConf = strings.Replace(newConf,
			"HOOKS=(base udev autodetect modconf kms keyboard keymap consolefont block filesystems fsck)",
			"HOOKS=(base udev autodetect keyboard keymap consolefont modconf kms block encrypt filesystems fsck)",
			1)
		newConf = strings.Replace(newConf,
			"HOOKS=(base udev autodetect modconf block filesystems keyboard fsck)",
			"HOOKS=(base udev autodetect keyboard keymap consolefont modconf block encrypt filesystems fsck)",
			1)

		if err := os.WriteFile("/mnt/etc/mkinitcpio.conf", []byte(newConf), 0644); err != nil {
			return fmt.Errorf("failed to write mkinitcpio.conf: %w", err)
		}

		if _, err := chroot([]string{"mkinitcpio", "-P"}); err != nil {
			return fmt.Errorf("mkinitcpio failed: %w", err)
		}
	}

	return nil
}

func (m InstallModel) installDesktop() error {
	// Install Cosmic Desktop as the only supported desktop environment
	cosmicPackages := []string{
		"cosmic-session",
		"cosmic-greeter",
		"cosmic-files",
		"cosmic-edit",
		"cosmic-term",
		"cosmic-store",
		"cosmic-settings",
		"xorg-xwayland",
		"xwayland-satellite", // XWayland-Satellite for better X11 app isolation
	}

	for _, pkg := range cosmicPackages {
		if _, err := chroot([]string{"pacman", "-Sy", "--noconfirm", pkg}); err != nil {
			return fmt.Errorf("failed to install %s: %w", pkg, err)
		}
	}

	// Enable Cosmic services
	services := []string{
		"cosmic-greeter",
		"NetworkManager",
	}

	for _, service := range services {
		if _, err := chroot([]string{"systemctl", "enable", service}); err != nil {
			return fmt.Errorf("failed to enable %s: %w", service, err)
		}
	}

	return nil
}

func (m InstallModel) runPostInstallScripts() error {
	// Copy overlay files if they exist
	overlayDir := "./overlay"
	if _, err := os.Stat(overlayDir); err == nil {
		fmt.Println("Applying system overlays...")
		cp := exec.Command("cp", "-r", overlayDir+"/.", "/mnt/")
		if output, err := cp.CombinedOutput(); err != nil {
			return fmt.Errorf("overlay copy failed: %w\nOutput: %s", err, output)
		}
	}

	// Run post-install scripts
	scriptsDir := "./scripts"
	entries, err := os.ReadDir(scriptsDir)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}

	// Sort scripts to ensure they run in order
	sort.Slice(entries, func(i, j int) bool {
		return entries[i].Name() < entries[j].Name()
	})

	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".sh") {
			continue
		}

		scriptPath := filepath.Join(scriptsDir, entry.Name())
		scriptContent, err := os.ReadFile(scriptPath)
		if err != nil {
			return err
		}

		// Copy script to chroot environment
		chrootScript := "/tmp/" + entry.Name()
		os.WriteFile("/mnt"+chrootScript, scriptContent, 0755)

		// Run script in chroot environment
		if output, err := chroot([]string{"sh", chrootScript}); err != nil {
			return fmt.Errorf("script %s failed: %w\nOutput: %s", entry.Name(), err, output)
		}

		// Clean up script
		os.Remove("/mnt" + chrootScript)
	}

	return nil
}

func chroot(args []string) (string, error) {
	cmd := exec.Command("arch-chroot", append([]string{"/mnt"}, args...)...)
	output, err := cmd.CombinedOutput()
	return string(output), err
}

func (m InstallModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case installCompleteMsg:
		if msg.err != nil {
			m.err = msg.err
			m.status = "Installation failed"
		} else {
			m.status = "Installation complete!"
		}
		m.Finished = true
		return m, tea.Quit
	}

	return m, nil
}

func (m InstallModel) View() string {
	var content strings.Builder

	content.WriteString(styles.TitleStyle.Render("⚙️  Installing Miasma OS"))
	content.WriteString("\n\n")

	progress := fmt.Sprintf("Step %d/%d", m.currentStep, m.totalSteps)
	content.WriteString(styles.ProgressTextStyle.Render(progress))
	content.WriteString("\n")

	bar := strings.Repeat("█", m.currentStep) + strings.Repeat("░", m.totalSteps-m.currentStep)
	content.WriteString(styles.ProgressBarStyle.Render(bar))
	content.WriteString("\n\n")

	content.WriteString(styles.SpinnerStyle.Render("● ") + m.status)
	content.WriteString("\n\n")

	if m.err != nil {
		errMsg := fmt.Sprintf("✗ Error: %v", m.err)
		content.WriteString(styles.ErrorBoxStyle.Render(errMsg))
		content.WriteString("\n\n")
		content.WriteString(styles.HelpStyle.Render("Press Ctrl+C to exit"))
	}

	if m.Finished && m.err == nil {
		success := "✓ Installation successful!\n\nPress Ctrl+C to exit and reboot."
		content.WriteString(styles.BoxStyle.Render(success))
		content.WriteString("\n\n")
		content.WriteString(styles.RenderHelp("Ctrl+C", "Exit and reboot"))
	}

	return styles.AppStyle.Render(content.String())
}

func readLines(path string) ([]string, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	var lines []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	return lines, scanner.Err()
}
