package main

import (
	"encoding/json"
	"errors"
	"testing"
	"time"
)

// waktuBeku dipakai seluruh test agar hasil perbandingan deterministik.
var waktuBeku = time.Date(2026, 1, 15, 8, 0, 0, 0, time.UTC)

// tugasContoh mengembalikan salinan segar daftar tugas untuk test.
func tugasContoh() []Task {
	return []Task{
		{ID: 1, Judul: "Belajar Go", Dibuat: waktuBeku},
		{ID: 2, Judul: "Menulis dokumentasi", Selesai: true, Dibuat: waktuBeku.Add(2 * time.Hour)},
		{ID: 3, Judul: "Rilis aplikasi", Dibuat: waktuBeku.Add(4 * time.Hour)},
	}
}

func TestValidasiJudul(t *testing.T) {
	cases := []struct {
		nama        string
		judul       string
		wantErrSent error
	}{
		{"judul normal", "Belajar Go", nil},
		{"judul dengan spasi di tepi", "  Belajar Go  ", nil},
		{"judul kosong", "", ErrJudulKosong},
		{"judul hanya spasi", "   ", ErrJudulKosong},
		{"judul hanya tab", "\t", ErrJudulKosong},
	}

	for _, c := range cases {
		t.Run(c.nama, func(t *testing.T) {
			err := ValidasiJudul(c.judul)

			if c.wantErrSent == nil {
				if err != nil {
					t.Fatalf("ValidasiJudul(%q) error tak terduga: %v", c.judul, err)
				}
				return
			}

			if !errors.Is(err, c.wantErrSent) {
				t.Errorf("ValidasiJudul(%q) error = %v; want %v", c.judul, err, c.wantErrSent)
			}
		})
	}
}

func TestEqual(t *testing.T) {
	cases := []struct {
		nama      string
		t, other  Task
		wantEqual bool
	}{
		{"isi sama persis", Task{ID: 1, Judul: "Belajar Go", Dibuat: waktuBeku}, Task{ID: 1, Judul: "Belajar Go", Dibuat: waktuBeku}, true},
		{"id berbeda", Task{ID: 1, Judul: "Belajar Go", Dibuat: waktuBeku}, Task{ID: 2, Judul: "Belajar Go", Dibuat: waktuBeku}, false},
		{"judul berbeda", Task{ID: 1, Judul: "Belajar Go", Dibuat: waktuBeku}, Task{ID: 1, Judul: "Belajar PHP", Dibuat: waktuBeku}, false},
		{"status berbeda", Task{ID: 1, Judul: "Belajar Go", Dibuat: waktuBeku}, Task{ID: 1, Judul: "Belajar Go", Selesai: true, Dibuat: waktuBeku}, false},
		{"waktu berbeda", Task{ID: 1, Judul: "Belajar Go", Dibuat: waktuBeku}, Task{ID: 1, Judul: "Belajar Go", Dibuat: waktuBeku.Add(time.Hour)}, false},
		{"instan sama zona beda", Task{ID: 1, Dibuat: waktuBeku}, Task{ID: 1, Dibuat: time.Date(2026, 1, 15, 15, 0, 0, 0, time.FixedZone("WIB", 7*60*60))}, true},
	}

	for _, c := range cases {
		t.Run(c.nama, func(t *testing.T) {
			if got := c.t.Equal(c.other); got != c.wantEqual {
				t.Errorf("Equal = %v; want %v", got, c.wantEqual)
			}
		})
	}
}

func TestBandingkan(t *testing.T) {
	cases := []struct {
		nama     string
		t, other Task
		wantSign int // -1, 0, atau 1
	}{
		{"dibuat lebih dulu", Task{ID: 1, Dibuat: waktuBeku}, Task{ID: 2, Dibuat: waktuBeku.Add(time.Hour)}, -1},
		{"dibuat lebih lambat", Task{ID: 2, Dibuat: waktuBeku.Add(time.Hour)}, Task{ID: 1, Dibuat: waktuBeku}, 1},
		{"waktu sama id lebih kecil", Task{ID: 1, Dibuat: waktuBeku}, Task{ID: 2, Dibuat: waktuBeku}, -1},
		{"waktu sama id lebih besar", Task{ID: 3, Dibuat: waktuBeku}, Task{ID: 2, Dibuat: waktuBeku}, 1},
	}

	for _, c := range cases {
		t.Run(c.nama, func(t *testing.T) {
			got := Bandingkan(c.t, c.other)
			if got == 0 || (got < 0) != (c.wantSign < 0) {
				t.Errorf("Bandingkan = %d; want tanda %d", got, c.wantSign)
			}
		})
	}
}

func TestIDBerikutnya(t *testing.T) {
	cases := []struct {
		nama  string
		tasks []Task
		want  int
	}{
		{"daftar kosong", nil, 1},
		{"berurutan", tugasContoh(), 4},
		{"id terbesar di tengah", []Task{{ID: 7}, {ID: 3}, {ID: 5}}, 8},
	}

	for _, c := range cases {
		t.Run(c.nama, func(t *testing.T) {
			if got := IDBerikutnya(c.tasks); got != c.want {
				t.Errorf("IDBerikutnya = %d; want %d", got, c.want)
			}
		})
	}
}

func TestTugasAktif(t *testing.T) {
	aktif := TugasAktif(tugasContoh())

	if len(aktif) != 2 {
		t.Fatalf("TugasAktif mengembalikan %d tugas; want 2", len(aktif))
	}
	for _, tsk := range aktif {
		if tsk.Selesai {
			t.Errorf("tugas %d selesai tetapi ikut terpilih", tsk.ID)
		}
	}

	if got := TugasAktif(nil); len(got) != 0 {
		t.Errorf("TugasAktif(nil) mengembalikan %d tugas; want 0", len(got))
	}
}

func TestCariIndeks(t *testing.T) {
	tasks := tugasContoh()

	if got := CariIndeks(tasks, 2); got != 1 {
		t.Errorf("CariIndeks(tasks, 2) = %d; want 1", got)
	}
	if got := CariIndeks(tasks, 9); got != -1 {
		t.Errorf("CariIndeks(tasks, 9) = %d; want -1", got)
	}
	if got := CariIndeks(nil, 1); got != -1 {
		t.Errorf("CariIndeks(nil, 1) = %d; want -1", got)
	}
}

func TestTaskMarshalJSON(t *testing.T) {
	b, err := json.Marshal(Task{ID: 1, Judul: "Belajar Go", Dibuat: waktuBeku})
	if err != nil {
		t.Fatalf("Marshal error tak terduga: %v", err)
	}

	want := `{"id":1,"judul":"Belajar Go","selesai":false,"dibuat":"2026-01-15T08:00:00Z"}`
	if string(b) != want {
		t.Errorf("Marshal = %s; want %s", b, want)
	}
}
