package cli

import (
	"bytes"
	"io"
	"log"
	"os"
	"syscall"
	"testing"
	"unsafe"
)

func withCapturedLog(t *testing.T) *bytes.Buffer {
	t.Helper()

	var buf bytes.Buffer
	oldWriter := log.Writer()
	oldFlags := log.Flags()
	log.SetOutput(&buf)
	log.SetFlags(0)
	t.Cleanup(func() {
		log.SetOutput(oldWriter)
		log.SetFlags(oldFlags)
	})
	return &buf
}

func withTerminalIoctl(t *testing.T, widths ...int) {
	t.Helper()

	oldIoctlFunc := ioctlFunc
	widthCall := 0
	ioctlFunc = func(fd, req uintptr, arg unsafe.Pointer) syscall.Errno {
		switch req {
		case uintptr(syscall.TCGETS):
			return 0
		case uintptr(syscall.TIOCGWINSZ):
			width := widths[len(widths)-1]
			if widthCall < len(widths) {
				width = widths[widthCall]
			}
			widthCall++
			if width <= 0 {
				return syscall.ENOTTY
			}
			ws := (*struct {
				row, col       uint16
				xpixel, ypixel uint16
			})(arg)
			ws.col = uint16(width)
			return 0
		default:
			t.Errorf("unexpected ioctl req %d for fd %d", req, fd)
			return syscall.EINVAL
		}
	}
	t.Cleanup(func() { ioctlFunc = oldIoctlFunc })
}

func TestProgressReporter_NonTerminalLogsOnly(t *testing.T) {
	tmp, err := os.CreateTemp("", "progress_reporter_test")
	if err != nil {
		t.Fatalf("CreateTemp failed: %v", err)
	}
	defer os.Remove(tmp.Name())
	defer tmp.Close()

	if IsTerminalFile(tmp) {
		t.Fatalf("expected temp file not to be a terminal")
	}

	logBuf := withCapturedLog(t)
	report := NewProgressReporter([]string{"task1", "task2"}, tmp)

	report("task1", "starting")
	report("task2", "running")
	report("task1", "done")

	if _, err := tmp.Seek(0, 0); err != nil {
		t.Fatalf("Seek failed: %v", err)
	}
	data, err := io.ReadAll(tmp)
	if err != nil {
		t.Fatalf("ReadAll failed: %v", err)
	}
	if got := string(data); got != "" {
		t.Fatalf("expected non-terminal reporter not to write to output, got %q", got)
	}

	wantLog := "" +
		"task1: starting\n" +
		"task2: running\n" +
		"task1: done\n"
	if got := logBuf.String(); got != wantLog {
		t.Errorf("unexpected log output.\nGot:\n%q\nWant:\n%q", got, wantLog)
	}
}

func TestProgressReporter_TerminalModeShowsAllTasks(t *testing.T) {
	withTerminalIoctl(t, 13)

	tmp, err := os.CreateTemp("", "progress_reporter_terminal")
	if err != nil {
		t.Fatalf("CreateTemp failed: %v", err)
	}
	defer os.Remove(tmp.Name())
	defer tmp.Close()

	logBuf := withCapturedLog(t)
	report := NewProgressReporter([]string{"task1", "task2"}, tmp)

	report("task1", "phase1")
	report("task2", "phase2")

	if _, err := tmp.Seek(0, 0); err != nil {
		t.Fatalf("Seek failed: %v", err)
	}
	data, err := io.ReadAll(tmp)
	if err != nil {
		t.Fatalf("ReadAll failed: %v", err)
	}

	const esc = "\033[F\033[K"
	wantOutput := "" +
		"\n" +
		"task1: phase1\n" +
		"task2: \n" +
		esc + esc + esc +
		"\n" +
		"task1: phase1\n" +
		"task2: phase2\n"
	if got := string(data); got != wantOutput {
		t.Errorf("unexpected terminal output.\nGot:\n%q\nWant:\n%q", got, wantOutput)
	}

	wantLog := "" +
		"task1: phase1\n" +
		"task2: phase2\n"
	if got := logBuf.String(); got != wantLog {
		t.Errorf("unexpected terminal log output.\nGot:\n%q\nWant:\n%q", got, wantLog)
	}
}

func TestProgressReporter_TerminalModeClearsWrappedRows(t *testing.T) {
	withTerminalIoctl(t, 10)

	tmp, err := os.CreateTemp("", "progress_reporter_terminal")
	if err != nil {
		t.Fatalf("CreateTemp failed: %v", err)
	}
	defer os.Remove(tmp.Name())
	defer tmp.Close()

	logBuf := withCapturedLog(t)
	report := NewProgressReporter([]string{"task1", "task2"}, tmp)

	report("task1", "123456789012345")
	report("task2", "x")

	if _, err := tmp.Seek(0, 0); err != nil {
		t.Fatalf("Seek failed: %v", err)
	}
	data, err := io.ReadAll(tmp)
	if err != nil {
		t.Fatalf("ReadAll failed: %v", err)
	}

	const esc = "\033[F\033[K"
	wantOutput := "" +
		"\n" +
		"task1: 123456789012345\n" +
		"task2: \n" +
		esc + esc + esc + esc + esc +
		"\n" +
		"task1: 123456789012345\n" +
		"task2: x\n"
	if got := string(data); got != wantOutput {
		t.Errorf("unexpected terminal output.\nGot:\n%q\nWant:\n%q", got, wantOutput)
	}

	wantLog := "" +
		"task1: 123456789012345\n" +
		"task2: x\n"
	if got := logBuf.String(); got != wantLog {
		t.Errorf("unexpected terminal log output.\nGot:\n%q\nWant:\n%q", got, wantLog)
	}
}

func TestProgressReporter_TerminalModeSingleTaskLogsOnly(t *testing.T) {
	withTerminalIoctl(t, 80)

	tmp, err := os.CreateTemp("", "progress_reporter_terminal")
	if err != nil {
		t.Fatalf("CreateTemp failed: %v", err)
	}
	defer os.Remove(tmp.Name())
	defer tmp.Close()

	logBuf := withCapturedLog(t)
	report := NewProgressReporter([]string{"task1"}, tmp)

	report("task1", "building")
	report("task1", "failed")

	if _, err := tmp.Seek(0, 0); err != nil {
		t.Fatalf("Seek failed: %v", err)
	}
	data, err := io.ReadAll(tmp)
	if err != nil {
		t.Fatalf("ReadAll failed: %v", err)
	}
	if got := string(data); got != "" {
		t.Errorf("expected single-task reporter not to write a table, got %q", got)
	}

	wantLog := "" +
		"task1: building\n" +
		"task1: failed\n"
	if got := logBuf.String(); got != wantLog {
		t.Errorf("unexpected terminal log output.\nGot:\n%q\nWant:\n%q", got, wantLog)
	}
}

func TestProgressReporter_TerminalModeStopsTableWithoutWidth(t *testing.T) {
	withTerminalIoctl(t, 10, 0)

	tmp, err := os.CreateTemp("", "progress_reporter_terminal")
	if err != nil {
		t.Fatalf("CreateTemp failed: %v", err)
	}
	defer os.Remove(tmp.Name())
	defer tmp.Close()

	logBuf := withCapturedLog(t)
	report := NewProgressReporter([]string{"task1", "task2"}, tmp)

	report("task1", "123456789012345")
	report("task2", "x")

	if _, err := tmp.Seek(0, 0); err != nil {
		t.Fatalf("Seek failed: %v", err)
	}
	data, err := io.ReadAll(tmp)
	if err != nil {
		t.Fatalf("ReadAll failed: %v", err)
	}

	const esc = "\033[F\033[K"
	wantOutput := "" +
		"\n" +
		"task1: 123456789012345\n" +
		"task2: \n" +
		esc + esc + esc + esc + esc
	if got := string(data); got != wantOutput {
		t.Errorf("unexpected terminal output.\nGot:\n%q\nWant:\n%q", got, wantOutput)
	}

	wantLog := "" +
		"task1: 123456789012345\n" +
		"task2: x\n"
	if got := logBuf.String(); got != wantLog {
		t.Errorf("unexpected terminal log output.\nGot:\n%q\nWant:\n%q", got, wantLog)
	}
}
