package steps

import (
	"bufio"
	"fmt"
	"io"
	"miasma-installer/tui/styles"
	"os"
	"os/exec"
	"strconv"
	"strings"

	"github.com/charmbracelet/bubbles/list"
	tea "github.com/charmbracelet/bubbletea"
)

type Disk struct {
	name        string
	description string
	size        string
}

func (i Disk) FilterValue() string { return i.name }
func (i Disk) Title() string       { return i.name }
func (i Disk) Description() string { return fmt.Sprintf("%s - %s", i.size, i.description) }

type ItemDelegate struct{}

func (i ItemDelegate) Height() int                               { return 1 }
func (i ItemDelegate) Spacing() int                              { return 0 }
func (i ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
	disk, ok := item.(Disk)
	if !ok {
		return
	}

	s := fmt.Sprintf("%d. %s - %s", index+1, disk.Title(), disk.Description())

	if index == m.Index() {
		s = styles.ActiveItemStyle.Render("> " + s)
	} else {
		s = styles.InactiveItemStyle.Render("  " + s)
	}
	fmt.Fprint(w, s)
}

type DiskSelModel struct {
	list         list.Model
	Finished     bool
	SelectedDisk string
	loading      bool
}

type disksLoadedMsg struct {
	disks []list.Item
}

func NewDiskSelModel(width, height int) DiskSelModel {
	l := list.New([]list.Item{}, ItemDelegate{}, width, height-5)
	l.Title = "Select Installation Disk"
	l.SetShowStatusBar(false)
	l.SetFilteringEnabled(false)
	l.Styles.Title = styles.TitleStyle

	return DiskSelModel{
		list:    l,
		loading: true,
	}
}

func (m DiskSelModel) Init() tea.Cmd {
	return loadDisks
}

func loadDisks() tea.Msg {
	disks, err := scanDisks()
	if err != nil {
		return disksLoadedMsg{disks: []list.Item{
			Disk{name: "error", description: err.Error(), size: ""},
		}}
	}
	return disksLoadedMsg{disks: disks}
}

func scanDisks() ([]list.Item, error) {
	cmd := exec.Command("lsblk", "-ndo", "NAME,SIZE,TYPE,MODEL")
	output, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("failed to list disks: %w", err)
	}

	var disks []list.Item
	scanner := bufio.NewScanner(strings.NewReader(string(output)))

	for scanner.Scan() {
		line := scanner.Text()
		fields := strings.Fields(line)
		if len(fields) < 3 {
			continue
		}

		name := fields[0]
		size := fields[1]
		diskType := fields[2]
		model := ""
		if len(fields) > 3 {
			model = strings.Join(fields[3:], " ")
		}

		if diskType != "disk" {
			continue
		}

		if strings.HasPrefix(name, "loop") || strings.HasPrefix(name, "sr") {
			continue
		}

		disks = append(disks, Disk{
			name:        "/dev/" + name,
			size:        size,
			description: model,
		})
	}

	if len(disks) == 0 {
		return nil, fmt.Errorf("no suitable disks found")
	}

	return disks, nil
}

func (m DiskSelModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	if m.Finished {
		return m, nil
	}

	switch msg := msg.(type) {
	case disksLoadedMsg:
		m.loading = false
		m.list.SetItems(msg.disks)
		return m, nil

	case tea.KeyMsg:
		if !m.loading && msg.String() == "enter" {
			if item, ok := m.list.SelectedItem().(Disk); ok {
				m.SelectedDisk = item.name
				m.Finished = true
				return m, nil
			}
		}
	}

	var cmd tea.Cmd
	m.list, cmd = m.list.Update(msg)
	return m, cmd
}

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

	if m.loading {
		content.WriteString(styles.TitleStyle.Render("💿 Scanning Disks..."))
		content.WriteString("\n\n")
		content.WriteString(styles.SpinnerStyle.Render("● Detecting available storage devices..."))
		return styles.AppStyle.Render(content.String())
	}

	content.WriteString(styles.TitleStyle.Render("💿 Select Installation Disk"))
	content.WriteString("\n\n")

	warning := "⚠️  All data on the selected disk will be erased!"
	content.WriteString(styles.WarningBoxStyle.Render(warning))
	content.WriteString("\n\n")

	content.WriteString(m.list.View())
	content.WriteString("\n\n")
	content.WriteString(styles.RenderHelp("↑/↓", "Navigate", "Enter", "Select"))

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

func formatBytes(bytes uint64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := uint64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

func getDiskSize(device string) (string, error) {
	data, err := os.ReadFile(fmt.Sprintf("/sys/block/%s/size", strings.TrimPrefix(device, "/dev/")))
	if err != nil {
		return "", err
	}

	sectors, err := strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
	if err != nil {
		return "", err
	}

	bytes := sectors * 512
	return formatBytes(bytes), nil
}
