package ui

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"fyne.io/fyne/v2"
	fyneapp "fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/layout"
	"fyne.io/fyne/v2/storage"
	"fyne.io/fyne/v2/widget"
	appcore "github.com/veggiedefender/torrent-client/internal/app"
	"github.com/veggiedefender/torrent-client/internal/torrent"
	"github.com/veggiedefender/torrent-client/internal/torrentfile"
)

type desktopUI struct {
	controller *appcore.Controller
	app        fyne.App
	window     fyne.Window

	pathEntry        *widget.Entry
	progress         *widget.ProgressBar
	phaseLabel       *widget.Label
	phaseMetricLabel *widget.Label
	messageLabel     *widget.Label
	errorLabel       *widget.Label
	nameLabel        *widget.Label
	sizeLabel        *widget.Label
	pieceLabel       *widget.Label
	pieceCountLabel  *widget.Label
	downloadedLabel  *widget.Label
	outputLabel      *widget.Label
	peerCountLabel   *widget.Label

	mu       sync.RWMutex
	trackers []torrent.TrackerStatus
	peers    []torrent.PeerStatus
	files    []torrentfile.File

	trackersTable *widget.Table
	peersTable    *widget.Table
	filesTable    *widget.Table

	stopRefresh chan struct{}
}

func Start(controller *appcore.Controller) {
	desktop := newDesktopUI(controller)
	desktop.window.ShowAndRun()
}

func newDesktopUI(controller *appcore.Controller) *desktopUI {
	a := fyneapp.NewWithID("github.com/veggiedefender/ztorrent")
	w := a.NewWindow("Ztorrent")
	w.Resize(fyne.NewSize(1200, 760))

	ui := &desktopUI{
		controller:       controller,
		app:              a,
		window:           w,
		stopRefresh:      make(chan struct{}),
		pathEntry:        widget.NewEntry(),
		progress:         widget.NewProgressBar(),
		phaseLabel:       widget.NewLabel("Phase: idle"),
		phaseMetricLabel: widget.NewLabel("idle"),
		messageLabel:     widget.NewLabel(""),
		errorLabel:       widget.NewLabel(""),
		nameLabel:        widget.NewLabel("-"),
		sizeLabel:        widget.NewLabel("-"),
		pieceLabel:       widget.NewLabel("-"),
		pieceCountLabel:  widget.NewLabel("-"),
		downloadedLabel:  widget.NewLabel("0 / 0"),
		outputLabel:      widget.NewLabel("-"),
		peerCountLabel:   widget.NewLabel("0"),
	}

	ui.pathEntry.SetPlaceHolder("/path/to/file.torrent")

	browseButton := widget.NewButton("Browse", ui.onBrowse)
	loadButton := widget.NewButton("Load Torrent", ui.onLoad)
	topRow := container.NewBorder(nil, nil, nil, container.NewHBox(browseButton, loadButton), ui.pathEntry)

	metaGrid := container.NewGridWithColumns(4,
		wrapMetric("Name", ui.nameLabel),
		wrapMetric("Size (bytes)", ui.sizeLabel),
		wrapMetric("Piece length", ui.pieceLabel),
		wrapMetric("Completed pieces", ui.pieceCountLabel),
		wrapMetric("Downloaded", ui.downloadedLabel),
		wrapMetric("Output", ui.outputLabel),
		wrapMetric("Discovered peers", ui.peerCountLabel),
		wrapMetric("Phase", ui.phaseMetricLabel),
	)

	ui.trackersTable = ui.newTrackersTable()
	ui.peersTable = ui.newPeersTable()
	ui.filesTable = ui.newFilesTable()

	tabs := container.NewAppTabs(
		container.NewTabItem("Trackers", container.NewMax(ui.trackersTable)),
		container.NewTabItem("Peers", container.NewMax(ui.peersTable)),
		container.NewTabItem("Files", container.NewMax(ui.filesTable)),
	)

	statusColumn := container.NewVBox(
		ui.phaseLabel,
		ui.progress,
		ui.messageLabel,
		ui.errorLabel,
	)

	content := container.NewBorder(
		container.NewVBox(topRow),
		nil,
		nil,
		nil,
		container.NewVBox(
			statusColumn,
			layout.NewSpacer(),
			metaGrid,
			layout.NewSpacer(),
			tabs,
		),
	)

	w.SetContent(content)
	w.SetOnClosed(func() {
		close(ui.stopRefresh)
	})

	if len(os.Args) > 1 {
		ui.pathEntry.SetText(os.Args[1])
		ui.onLoad()
	}

	go ui.refreshLoop()
	return ui
}

func wrapMetric(name string, value fyne.CanvasObject) fyne.CanvasObject {
	return container.NewVBox(widget.NewLabel(name), value)
}

func (ui *desktopUI) onBrowse() {
	filter := storage.NewExtensionFileFilter([]string{".torrent"})
	fd := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) {
		if err != nil {
			ui.setError(err.Error())
			return
		}
		if reader == nil {
			return
		}
		defer reader.Close()

		ui.pathEntry.SetText(reader.URI().Path())
	}, ui.window)
	fd.SetFilter(filter)
	fd.Show()
}

func (ui *desktopUI) onLoad() {
	path := strings.TrimSpace(ui.pathEntry.Text)
	if path == "" {
		ui.setError("select a .torrent file first")
		return
	}

	ui.setMessage("Loading torrent...")
	ui.setError("")

	go func() {
		err := ui.controller.StartTorrent(path)
		status := ui.controller.Status()
		ui.updateFromStatus(status)
		if err != nil {
			ui.setError(err.Error())
		} else {
			ui.setMessage("Loaded " + filepath.Base(path))
		}
	}()
}

func (ui *desktopUI) refreshLoop() {
	ticker := time.NewTicker(1200 * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case <-ui.stopRefresh:
			return
		case <-ticker.C:
			ui.updateFromStatus(ui.controller.Status())
		}
	}
}

func (ui *desktopUI) updateFromStatus(status torrent.Status) {
	fyne.Do(func() {
		ui.progress.SetValue(status.Progress)
		ui.phaseLabel.SetText("Phase: " + status.Phase)
		ui.phaseMetricLabel.SetText(status.Phase)
		ui.nameLabel.SetText(status.Name)
		ui.sizeLabel.SetText(strconv.Itoa(status.Length))
		ui.pieceLabel.SetText(strconv.Itoa(status.PieceLength))
		if status.PieceCount > 0 {
			ui.pieceCountLabel.SetText(fmt.Sprintf("%d / %d", status.CompletedPieces, status.PieceCount))
		} else {
			ui.pieceCountLabel.SetText("-")
		}
		ui.downloadedLabel.SetText(fmt.Sprintf("%d / %d", status.DownloadedBytes, status.TotalBytes))
		ui.outputLabel.SetText(status.OutputPath)
		ui.peerCountLabel.SetText(strconv.Itoa(status.PeerCount))

		ui.mu.Lock()
		ui.trackers = append(ui.trackers[:0], status.Trackers...)
		ui.peers = append(ui.peers[:0], status.Peers...)
		ui.files = append(ui.files[:0], status.Files...)
		ui.mu.Unlock()

		ui.trackersTable.Refresh()
		ui.peersTable.Refresh()
		ui.filesTable.Refresh()

		if status.LastError != "" {
			ui.errorLabel.SetText(status.LastError)
		} else {
			ui.errorLabel.SetText("")
		}
	})
}

func (ui *desktopUI) setMessage(msg string) {
	fyne.Do(func() {
		ui.messageLabel.SetText(msg)
	})
}

func (ui *desktopUI) setError(msg string) {
	fyne.Do(func() {
		if msg == "" {
			ui.errorLabel.SetText("")
			return
		}
		ui.errorLabel.SetText("Error: " + msg)
	})
}

func (ui *desktopUI) newTrackersTable() *widget.Table {
	table := widget.NewTable(
		func() (int, int) {
			ui.mu.RLock()
			defer ui.mu.RUnlock()
			return len(ui.trackers) + 1, 4
		},
		func() fyne.CanvasObject {
			return widget.NewLabel("")
		},
		func(id widget.TableCellID, obj fyne.CanvasObject) {
			label := obj.(*widget.Label)
			if id.Row == 0 {
				switch id.Col {
				case 0:
					label.SetText("Tracker")
				case 1:
					label.SetText("State")
				case 2:
					label.SetText("Peers")
				case 3:
					label.SetText("Error")
				}
				return
			}

			ui.mu.RLock()
			defer ui.mu.RUnlock()
			tr := ui.trackers[id.Row-1]
			switch id.Col {
			case 0:
				label.SetText(tr.URL)
			case 1:
				label.SetText(tr.State)
			case 2:
				label.SetText(strconv.Itoa(tr.PeerCount))
			case 3:
				label.SetText(tr.Error)
			}
		},
	)
	table.SetColumnWidth(0, 420)
	table.SetColumnWidth(1, 90)
	table.SetColumnWidth(2, 70)
	table.SetColumnWidth(3, 420)
	return table
}

func (ui *desktopUI) newPeersTable() *widget.Table {
	table := widget.NewTable(
		func() (int, int) {
			ui.mu.RLock()
			defer ui.mu.RUnlock()
			return len(ui.peers) + 1, 6
		},
		func() fyne.CanvasObject {
			return widget.NewLabel("")
		},
		func(id widget.TableCellID, obj fyne.CanvasObject) {
			label := obj.(*widget.Label)
			if id.Row == 0 {
				switch id.Col {
				case 0:
					label.SetText("Address")
				case 1:
					label.SetText("Port")
				case 2:
					label.SetText("State")
				case 3:
					label.SetText("Pieces")
				case 4:
					label.SetText("Source tracker")
				case 5:
					label.SetText("Error")
				}
				return
			}

			ui.mu.RLock()
			defer ui.mu.RUnlock()
			peer := ui.peers[id.Row-1]
			switch id.Col {
			case 0:
				label.SetText(peer.Address)
			case 1:
				label.SetText(strconv.Itoa(int(peer.Port)))
			case 2:
				label.SetText(peer.State)
			case 3:
				label.SetText(strconv.Itoa(peer.DownloadedPieces))
			case 4:
				label.SetText(peer.Source)
			case 5:
				label.SetText(peer.Error)
			}
		},
	)
	table.SetColumnWidth(0, 170)
	table.SetColumnWidth(1, 80)
	table.SetColumnWidth(2, 100)
	table.SetColumnWidth(3, 70)
	table.SetColumnWidth(4, 310)
	table.SetColumnWidth(5, 440)
	return table
}

func (ui *desktopUI) newFilesTable() *widget.Table {
	table := widget.NewTable(
		func() (int, int) {
			ui.mu.RLock()
			defer ui.mu.RUnlock()
			return len(ui.files) + 1, 2
		},
		func() fyne.CanvasObject {
			return widget.NewLabel("")
		},
		func(id widget.TableCellID, obj fyne.CanvasObject) {
			label := obj.(*widget.Label)
			if id.Row == 0 {
				switch id.Col {
				case 0:
					label.SetText("File")
				case 1:
					label.SetText("Size")
				}
				return
			}

			ui.mu.RLock()
			defer ui.mu.RUnlock()
			file := ui.files[id.Row-1]
			switch id.Col {
			case 0:
				label.SetText(file.Path)
			case 1:
				label.SetText(fmt.Sprintf("%d", file.Length))
			}
		},
	)
	table.SetColumnWidth(0, 780)
	table.SetColumnWidth(1, 120)
	return table
}
