package goblin

import (
	"os"
	"reflect"
	"strings"
	"testing"
	"time"
)

func TestAsync(t *testing.T) {
	fakeTest := testing.T{}
	g := Goblin(&fakeTest)

	g.Describe("Async test", func() {
		g.It("Should fail when Fail is called immediately", func(done Done) {
			g.Fail("Failed")
		})
		g.It("Should fail when Fail is called", func(done Done) {
			go func() {
				time.Sleep(100 * time.Millisecond)
				g.Fail("foo is not bar")
			}()
		})

		g.It("Should fail if done receives a parameter", func(done Done) {
			go func() {
				time.Sleep(100 * time.Millisecond)
				done("Error")
			}()
		})

		g.It("Should pass when done is called", func(done Done) {
			go func() {
				time.Sleep(100 * time.Millisecond)
				done()
			}()
		})

		g.It("Should fail if done has been called multiple times", func(done Done) {
			go func() {
				time.Sleep(100 * time.Millisecond)
				done()
				done()
			}()
		})
	})

	if !fakeTest.Failed() {
		t.Fatal("Failed")
	}
}

func TestAsyncSequence(t *testing.T) {
	fakeTest := testing.T{}
	g := Goblin(&fakeTest)
	var sequence []string

	g.Describe("Async test", func() {

		g.BeforeEach(func() {
			sequence = append(sequence, "global_before_each")
		})

		g.AfterEach(func() {
			sequence = append(sequence, "global_after_each")
		})

		g.Describe("nested", func() {

			g.BeforeEach(func() {
				sequence = append(sequence, "local_before_each")
			})

			g.AfterEach(func() {
				sequence = append(sequence, "local_after_each")
			})

			g.It("Should fail when Fail is called", func(done Done) {
				go func() {
					time.Sleep(100 * time.Millisecond)
					sequence = append(sequence, "test")
					done()
				}()
			})
		})

	})

	expected := []string{
		"global_before_each",
		"local_before_each",
		"test",
		"local_after_each",
		"global_after_each",
	}
	if !reflect.DeepEqual(expected, sequence) {
		t.Fatalf("Failed, expected:\n%s\n\ngot: %s\n",
			strings.Join(expected, "\n"),
			strings.Join(sequence, "\n"),
		)
	}
}

func TestAddNumbersSucceed(t *testing.T) {
	fakeTest := testing.T{}
	g := Goblin(&fakeTest)

	g.Describe("Numbers", func() {
		g.It("Should add numbers", func() {
			sum := 1 + 1
			g.Assert(sum).Equal(2)
		})
	})

	if fakeTest.Failed() {
		t.Fatal("Failed")
	}
}

func TestAddNumbersFails(t *testing.T) {
	fakeTest := testing.T{}

	g := Goblin(&fakeTest)

	g.Describe("Numbers", func() {
		g.It("Should add numbers", func() {
			sum := 1 + 1
			g.Assert(sum).Equal(4)
		})
	})

	if !fakeTest.Failed() {
		t.Fatal("Failed")
	}
}

func TestMultipleIts(t *testing.T) {
	fakeTest := testing.T{}

	g := Goblin(&fakeTest)

	count := 0
	g.Describe("Numbers", func() {
		g.It("Should add numbers", func() {
			count++
			sum := 1 + 1
			g.Assert(sum).Equal(2)
		})

		g.It("Should add numbers", func() {
			count++
			sum := 1 + 1
			g.Assert(sum).Equal(4)
		})
	})

	if count != 2 {
		t.Fatal("Failed")
	}
}

func TestMultipleDescribes(t *testing.T) {
	fakeTest := testing.T{}

	g := Goblin(&fakeTest)

	count := 0
	g.Describe("Numbers", func() {

		g.Describe("Addition", func() {
			g.It("Should add numbers", func() {
				count++
				sum := 1 + 1
				g.Assert(sum).Equal(2)
			})
		})

		g.Describe("Subtraction", func() {
			g.It("Should subtract numbers", func() {
				count++
				sub := 5 - 5
				g.Assert(sub).Equal(1)
			})
		})
	})

	if count != 2 {
		t.Fatal("Failed")
	}
}

func TestPending(t *testing.T) {
	fakeTest := testing.T{}

	g := Goblin(&fakeTest)

	g.Describe("Numbers", func() {

		g.It("Should add numbers")

		g.Describe("Subtraction", func() {
			g.It("Should subtract numbers")
		})

	})

	if fakeTest.Failed() {
		t.Fatal("Failed")
	}
}

func TestExcluded(t *testing.T) {
	fakeTest := testing.T{}

	g := Goblin(&fakeTest)

	count := 0
	g.Describe("Numbers", func() {

		g.Xit("Should add numbers", func() {
			count++
			sum := 1 + 1
			g.Assert(sum).Equal(2)
		})

		g.Describe("Subtraction", func() {
			g.Xit("Should subtract numbers", func() {
				count++
				sub := 5 - 5
				g.Assert(sub).Equal(1)
			})
		})

	})

	if count != 0 {
		t.Fatal("Failed")
	}

	if fakeTest.Failed() {
		t.Fatal("Failed")
	}
}

func TestJustBeforeEach(t *testing.T) {
	fakeTest := testing.T{}

	g := Goblin(&fakeTest)
	const (
		before = iota
		beforeEach
		nBeforeEach
		justBeforeEach
		nJustBeforeEach
		it
		nIt
	)

	var (
		res [9]int
		i   int
	)

	g.Describe("Outer", func() {
		g.Before(func() {
			res[i] = before
			i++
		})

		g.BeforeEach(func() {
			res[i] = beforeEach
			i++
		})

		g.JustBeforeEach(func() {
			res[i] = justBeforeEach
			i++
		})

		g.It("should run all before handles by now", func() {
			res[i] = it
			i++
		})

		g.Describe("Nested", func() {
			g.BeforeEach(func() {
				res[i] = nBeforeEach
				i++
			})

			g.JustBeforeEach(func() {
				res[i] = nJustBeforeEach
				i++
			})

			g.It("should run all before handles by now", func() {
				res[i] = nIt
				i++
			})
		})
	})

	expected := [...]int{
		before,
		beforeEach,
		justBeforeEach,
		it,
		beforeEach,
		nBeforeEach,
		justBeforeEach,
		nJustBeforeEach,
		nIt,
	}

	if res != expected {
		t.Fatalf("expected %v to equal %v", res, expected)
	}
}

func TestNotRunBeforesOrAfters(t *testing.T) {
	fakeTest := testing.T{}

	g := Goblin(&fakeTest)
	var count int

	g.Describe("Numbers", func() {
		g.Before(func() {
			count++
		})

		g.BeforeEach(func() {
			count++
		})

		g.JustBeforeEach(func() {
			count++
		})

		g.After(func() {
			count++
		})
		g.AfterEach(func() {
			count++
		})

		g.Describe("Letters", func() {
			g.Before(func() {
				count++
			})

			g.BeforeEach(func() {
				count++
			})

			g.JustBeforeEach(func() {
				count++
			})

			g.After(func() {
				count++
			})
			g.AfterEach(func() {
				count++
			})
		})
	})

	if count != 0 {
		t.Fatal("Failed")
	}
}

func TestFailOnError(t *testing.T) {
	fakeTest := testing.T{}

	g := Goblin(&fakeTest)

	g.Describe("Numbers", func() {
		g.It("Does something", func() {
			g.Fail("Something")
		})
	})

	g.Describe("Errors", func() {
		g.It("Should fail with structs", func() {
			var s struct{ error string }
			s.error = "Error"
			g.Fail(s)
		})
	})

	if !fakeTest.Failed() {
		t.Fatal("Failed")
	}
}

func TestFailfOnError(t *testing.T) {
	fakeTest := testing.T{}

	g := Goblin(&fakeTest)

	g.Describe("Numbers", func() {
		g.It("Does something", func() {
			g.Failf("Something goes %s", "wrong")
		})
	})

	if !fakeTest.Failed() {
		t.Fatal("Failed")
	}
}

func TestRegex(t *testing.T) {
	fakeTest := testing.T{}
	os.Args = append(os.Args, "-goblin.run=matches")
	parseFlags()
	g := Goblin(&fakeTest)

	g.Describe("Test", func() {
		g.It("Doesn't match regex", func() {
			g.Fail("Regex shouldn't match")
		})

		g.It("It matches regex", func() {})
		g.It("It also matches", func() {})
	})

	if fakeTest.Failed() {
		t.Fatal("Failed")
	}

	// Reset the regex so other tests can run
	runRegex = nil
}

func TestFailImmediately(t *testing.T) {
	fakeTest := testing.T{}
	g := Goblin(&fakeTest)
	reached := false
	g.Describe("Errors", func() {
		g.It("Should fail immediately for sync test", func() {
			g.Assert(false).IsTrue()
			reached = true
			g.Assert("foo").Equal("bar")
		})
		g.It("Should fail immediately for async test", func(done Done) {
			go func() {
				g.Assert(false).IsTrue()
				reached = true
				g.Assert("foo").Equal("bar")
				done()
			}()
		})
	})

	if reached {
		t.Fatal("Failed")
	}
}

func TestTimeout(t *testing.T) {
	fakeTest := testing.T{}
	os.Args = append(os.Args, "-goblin.timeout=10ms", "-goblin.run=")
	parseFlags()
	g := Goblin(&fakeTest)

	g.Describe("Test", func() {
		g.It("Should fail if test exceeds the specified timeout with sync test", func() {
			time.Sleep(100 * time.Millisecond)
		})

		g.It("Should fail if test exceeds the specified timeout with async test", func(done Done) {
			time.Sleep(100 * time.Millisecond)
			done()
		})
	})

	if !fakeTest.Failed() {
		t.Fatal("Failed")
	}
}

func TestItTimeout(t *testing.T) {
	fakeTest := testing.T{}
	os.Args = append(os.Args, "-goblin.timeout=10ms")
	parseFlags()
	g := Goblin(&fakeTest)

	g.Describe("Test", func() {
		g.It("Should override default timeout", func() {
			g.Timeout(20 * time.Millisecond)
			time.Sleep(15 * time.Millisecond)
		})

		g.It("Should revert for different it", func() {
			g.Assert(g.timeout).Equal(10 * time.Millisecond)
		})

	})
	if fakeTest.Failed() {
		t.Fatal("Failed")
	}
}

func TestIsNilAndIsNotNil(t *testing.T) {
	fakeTest := testing.T{}
	g := Goblin(&fakeTest)

	g.Describe("Test for IsNil", func() {
		g.It("Should assert successfully with nil value", func() {
			g.Assert(nil).IsNil()
		})
	})

	g.Describe("Test for IsNotNil", func() {
		g.It("Should assert successfully with not nil value", func() {
			g.Assert(struct{}{}).IsNotNil()
		})
	})

	if fakeTest.Failed() {
		t.Fatal("Failed")
	}

	g.Describe("Test for IsNil with failed assertion", func() {
		g.It("Should fail", func() {
			g.Assert(100).IsNil()
		})
	})

	g.Describe("Test for IsNotNil with failed assertion", func() {
		g.It("Should fail", func() {
			g.Assert(nil).IsNotNil()
		})
	})

	if !fakeTest.Failed() {
		t.Fatal("Failed")
	}
}

func TestIsZeroAndIsNotZero(t *testing.T) {
	fakeTest := testing.T{}
	g := Goblin(&fakeTest)

	g.Describe("Test for IsZero", func() {
		g.It("Should assert successfully with int zero value", func() {
			g.Assert(0).IsZero()
		})

		g.It("Should assert successfully with float zero value", func() {
			g.Assert(0.0).IsZero()
		})

		g.It("Should assert successfully with string zero value", func() {
			g.Assert("").IsZero()
		})

		g.It("Should assert successfully with struct zero value", func() {
			g.Assert(struct{}{}).IsZero()
		})

		g.It("Should assert successfully with struct field with zero value", func() {
			g.Assert(struct{ value int }{value: 0}).IsZero()
		})
	})

	g.Describe("Test for IsNotZero", func() {
		g.It("Should assert successfully with int not zero value", func() {
			g.Assert(1).IsNotZero()
		})

		g.It("Should assert successfully with float  not zero value", func() {
			g.Assert(0.5).IsNotZero()
		})

		g.It("Should assert successfully with string not zero value", func() {
			g.Assert("ABC").IsNotZero()
		})

		g.It("Should assert successfully with struct not zero value", func() {
			g.Assert(struct{ value int }{value: 1}).IsNotZero()
		})
	})

	if fakeTest.Failed() {
		t.Fatal("Failed")
	}

	g.Describe("Test for IsZero with failed assertion", func() {
		g.It("Should fail", func() {
			g.Assert(100).IsZero()
		})

		g.It("Should fail", func() {
			g.Assert(1.0).IsZero()
		})

		g.It("Should fail", func() {
			g.Assert("A").IsZero()
		})

		g.It("Should fail", func() {
			g.Assert(struct{ value int }{value: 1}).IsZero()
		})
	})

	g.Describe("Test for IsNotZero with failed assertion", func() {
		g.It("Should fail", func() {
			g.Assert(0).IsNotZero()
		})

		g.It("Should fail", func() {
			g.Assert(0.0).IsNotZero()
		})

		g.It("Should fail", func() {
			g.Assert("").IsNotZero()
		})

		g.It("Should fail", func() {
			g.Assert(struct{}{}).IsNotZero()
		})
	})

	if !fakeTest.Failed() {
		t.Fatal("Failed")
	}
}
