package main

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"testing"
)

func TestTambah(t *testing.T) {
	cases := []struct {
		nama       string
		a, b, want float64
	}{
		{"dua bilangan bulat", 2, 3, 5},
		{"dua bilangan negatif", -4, -6, -10},
		{"campuran positif negatif", 7, -2, 5},
		{"bilangan desimal", 1.5, 2.25, 3.75},
	}

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

func TestKurang(t *testing.T) {
	cases := []struct {
		a, b, want float64
	}{
		{10, 4, 6},
		{2, 5, -3},
		{-1.5, -2.5, 1},
	}

	for _, c := range cases {
		if got := Kurang(c.a, c.b); got != c.want {
			t.Errorf("Kurang(%g, %g) = %g; want %g", c.a, c.b, got, c.want)
		}
	}
}

func TestKali(t *testing.T) {
	cases := []struct {
		a, b, want float64
	}{
		{6, 7, 42},
		{-3, 4, -12},
		{0.5, 8, 4},
		{0, 99, 0},
	}

	for _, c := range cases {
		if got := Kali(c.a, c.b); got != c.want {
			t.Errorf("Kali(%g, %g) = %g; want %g", c.a, c.b, got, c.want)
		}
	}
}

func TestBagi(t *testing.T) {
	cases := []struct {
		nama        string
		a, b, want  float64
		wantErrSent error
	}{
		{"hasil desimal", 10, 4, 2.5, nil},
		{"hasil negatif", -9, 3, -3, nil},
		{"dibagi satu", 7.5, 1, 7.5, nil},
		{"pembagian dengan nol", 10, 0, 0, ErrPembagianNol},
	}

	for _, c := range cases {
		t.Run(c.nama, func(t *testing.T) {
			got, err := Bagi(c.a, c.b)

			if c.wantErrSent != nil {
				if !errors.Is(err, c.wantErrSent) {
					t.Fatalf("Bagi(%g, %g) error = %v; want %v", c.a, c.b, err, c.wantErrSent)
				}
				return
			}

			if err != nil {
				t.Fatalf("Bagi(%g, %g) error tak terduga: %v", c.a, c.b, err)
			}
			if got != c.want {
				t.Errorf("Bagi(%g, %g) = %g; want %g", c.a, c.b, got, c.want)
			}
		})
	}
}

func TestOperasiValid(t *testing.T) {
	for _, operasi := range []string{"tambah", "kurang", "kali", "bagi"} {
		if !OperasiValid(operasi) {
			t.Errorf("OperasiValid(%q) = false; want true", operasi)
		}
	}

	for _, operasi := range []string{"", "pangkat", "TAMBAH", "akar"} {
		if OperasiValid(operasi) {
			t.Errorf("OperasiValid(%q) = true; want false", operasi)
		}
	}
}

func TestParseInput(t *testing.T) {
	cases := []struct {
		nama             string
		operasi, teksA   string
		teksB            string
		wantA, wantB     float64
		wantErrSepenggal string
	}{
		{"input lengkap", "tambah", "2", "3", 2, 3, ""},
		{"angka desimal", "bagi", "10", "2.5", 10, 2.5, ""},
		{"angka negatif", "kurang", "-8", "2", -8, 2, ""},
		{"operasi tidak dikenal", "pangkat", "2", "3", 0, 0, "tidak dikenal"},
		{"angka pertama salah", "tambah", "abc", "3", 0, 0, "angka pertama"},
		{"angka kedua salah", "bagi", "10", "x", 0, 0, "angka kedua"},
	}

	for _, c := range cases {
		t.Run(c.nama, func(t *testing.T) {
			a, b, err := ParseInput(c.operasi, c.teksA, c.teksB)

			if c.wantErrSepenggal == "" {
				if err != nil {
					t.Fatalf("ParseInput(%q, %q, %q) error tak terduga: %v", c.operasi, c.teksA, c.teksB, err)
				}
				if a != c.wantA || b != c.wantB {
					t.Errorf("ParseInput(%q, %q, %q) = (%g, %g); want (%g, %g)",
						c.operasi, c.teksA, c.teksB, a, b, c.wantA, c.wantB)
				}
				return
			}

			if err == nil || !strings.Contains(err.Error(), c.wantErrSepenggal) {
				t.Errorf("ParseInput(%q, %q, %q) error = %v; want pesan memuat %q",
					c.operasi, c.teksA, c.teksB, err, c.wantErrSepenggal)
			}
		})
	}
}

func TestParseInputMembungkusErrSyntax(t *testing.T) {
	_, _, err := ParseInput("tambah", "abc", "3")
	if !errors.Is(err, strconv.ErrSyntax) {
		t.Fatalf("ParseInput(\"tambah\", \"abc\", \"3\") error = %v; want error yang membungkus strconv.ErrSyntax", err)
	}
}

func ExampleTambah() {
	fmt.Println(Tambah(2, 3))
	// Output: 5
}

func ExampleBagi() {
	hasil, err := Bagi(10, 4)
	if err != nil {
		fmt.Println("galat:", err)
		return
	}
	fmt.Println(hasil)
	// Output: 2.5
}

func ExampleBagi_nol() {
	_, err := Bagi(10, 0)
	fmt.Println("galat:", err)
	// Output: galat: pembagian dengan nol
}
