package remote_test

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"time"

	"github.com/onsi/ginkgo/config"
	. "github.com/onsi/ginkgo/internal/remote"
	st "github.com/onsi/ginkgo/reporters/stenographer"
	"github.com/onsi/ginkgo/types"
)

var _ = Describe("Aggregator", func() {
	var (
		aggregator     *Aggregator
		reporterConfig config.DefaultReporterConfigType
		stenographer   *st.FakeStenographer
		result         chan bool

		ginkgoConfig1 config.GinkgoConfigType
		ginkgoConfig2 config.GinkgoConfigType

		suiteSummary1 *types.SuiteSummary
		suiteSummary2 *types.SuiteSummary

		beforeSummary *types.SetupSummary
		afterSummary  *types.SetupSummary
		specSummary   *types.SpecSummary

		suiteDescription string
	)

	BeforeEach(func() {
		reporterConfig = config.DefaultReporterConfigType{
			NoColor:           false,
			SlowSpecThreshold: 0.1,
			NoisyPendings:     true,
			Succinct:          false,
			Verbose:           true,
		}
		stenographer = st.NewFakeStenographer()
		result = make(chan bool, 1)
		aggregator = NewAggregator(2, result, reporterConfig, stenographer)

		//
		// now set up some fixture data
		//

		ginkgoConfig1 = config.GinkgoConfigType{
			RandomSeed:        1138,
			RandomizeAllSpecs: true,
			ParallelNode:      1,
			ParallelTotal:     2,
		}

		ginkgoConfig2 = config.GinkgoConfigType{
			RandomSeed:        1138,
			RandomizeAllSpecs: true,
			ParallelNode:      2,
			ParallelTotal:     2,
		}

		suiteDescription = "My Parallel Suite"

		suiteSummary1 = &types.SuiteSummary{
			SuiteDescription: suiteDescription,

			NumberOfSpecsBeforeParallelization: 30,
			NumberOfTotalSpecs:                 17,
			NumberOfSpecsThatWillBeRun:         15,
			NumberOfPendingSpecs:               1,
			NumberOfSkippedSpecs:               1,
		}

		suiteSummary2 = &types.SuiteSummary{
			SuiteDescription: suiteDescription,

			NumberOfSpecsBeforeParallelization: 30,
			NumberOfTotalSpecs:                 13,
			NumberOfSpecsThatWillBeRun:         8,
			NumberOfPendingSpecs:               2,
			NumberOfSkippedSpecs:               3,
		}

		beforeSummary = &types.SetupSummary{
			State:          types.SpecStatePassed,
			CapturedOutput: "BeforeSuiteOutput",
		}

		afterSummary = &types.SetupSummary{
			State:          types.SpecStatePassed,
			CapturedOutput: "AfterSuiteOutput",
		}

		specSummary = &types.SpecSummary{
			State:          types.SpecStatePassed,
			CapturedOutput: "SpecOutput",
		}
	})

	call := func(method string, args ...interface{}) st.FakeStenographerCall {
		return st.NewFakeStenographerCall(method, args...)
	}

	beginSuite := func() {
		stenographer.Reset()
		aggregator.SpecSuiteWillBegin(ginkgoConfig2, suiteSummary2)
		aggregator.SpecSuiteWillBegin(ginkgoConfig1, suiteSummary1)
		Eventually(func() interface{} {
			return len(stenographer.Calls())
		}).Should(BeNumerically(">=", 3))
	}

	Describe("Announcing the beginning of the suite", func() {
		Context("When one of the parallel-suites starts", func() {
			BeforeEach(func() {
				aggregator.SpecSuiteWillBegin(ginkgoConfig2, suiteSummary2)
			})

			It("should be silent", func() {
				Consistently(func() interface{} { return stenographer.Calls() }).Should(BeEmpty())
			})
		})

		Context("once all of the parallel-suites have started", func() {
			BeforeEach(func() {
				aggregator.SpecSuiteWillBegin(ginkgoConfig2, suiteSummary2)
				aggregator.SpecSuiteWillBegin(ginkgoConfig1, suiteSummary1)
				Eventually(func() interface{} {
					return stenographer.Calls()
				}).Should(HaveLen(3))
			})

			It("should announce the beginning of the suite", func() {
				Ω(stenographer.Calls()).Should(HaveLen(3))
				Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceSuite", suiteDescription, ginkgoConfig1.RandomSeed, true, false)))
				Ω(stenographer.Calls()[1]).Should(Equal(call("AnnounceTotalNumberOfSpecs", 30, false)))
				Ω(stenographer.Calls()[2]).Should(Equal(call("AnnounceAggregatedParallelRun", 2, false)))
			})
		})
	})

	Describe("Announcing specs and before suites", func() {
		Context("when the parallel-suites have not all started", func() {
			BeforeEach(func() {
				aggregator.BeforeSuiteDidRun(beforeSummary)
				aggregator.AfterSuiteDidRun(afterSummary)
				aggregator.SpecDidComplete(specSummary)
			})

			It("should not announce any specs", func() {
				Consistently(func() interface{} { return stenographer.Calls() }).Should(BeEmpty())
			})

			Context("when the parallel-suites subsequently start", func() {
				BeforeEach(func() {
					beginSuite()
				})

				It("should announce the specs, the before suites and the after suites", func() {
					Eventually(func() interface{} {
						return stenographer.Calls()
					}).Should(ContainElement(call("AnnounceSuccesfulSpec", specSummary)))

					Ω(stenographer.Calls()).Should(ContainElement(call("AnnounceCapturedOutput", beforeSummary.CapturedOutput)))
					Ω(stenographer.Calls()).Should(ContainElement(call("AnnounceCapturedOutput", afterSummary.CapturedOutput)))
				})
			})
		})

		Context("When the parallel-suites have all started", func() {
			BeforeEach(func() {
				beginSuite()
				stenographer.Reset()
			})

			Context("When a spec completes", func() {
				BeforeEach(func() {
					aggregator.BeforeSuiteDidRun(beforeSummary)
					aggregator.SpecDidComplete(specSummary)
					aggregator.AfterSuiteDidRun(afterSummary)
					Eventually(func() interface{} {
						return stenographer.Calls()
					}).Should(HaveLen(5))
				})

				It("should announce the captured output of the BeforeSuite", func() {
					Ω(stenographer.Calls()[0]).Should(Equal(call("AnnounceCapturedOutput", beforeSummary.CapturedOutput)))
				})

				It("should announce that the spec will run (when in verbose mode)", func() {
					Ω(stenographer.Calls()[1]).Should(Equal(call("AnnounceSpecWillRun", specSummary)))
				})

				It("should announce the captured stdout of the spec", func() {
					Ω(stenographer.Calls()[2]).Should(Equal(call("AnnounceCapturedOutput", specSummary.CapturedOutput)))
				})

				It("should announce completion", func() {
					Ω(stenographer.Calls()[3]).Should(Equal(call("AnnounceSuccesfulSpec", specSummary)))
				})

				It("should announce the captured output of the AfterSuite", func() {
					Ω(stenographer.Calls()[4]).Should(Equal(call("AnnounceCapturedOutput", afterSummary.CapturedOutput)))
				})
			})
		})
	})

	Describe("Announcing the end of the suite", func() {
		BeforeEach(func() {
			beginSuite()
			stenographer.Reset()
		})

		Context("When one of the parallel-suites ends", func() {
			BeforeEach(func() {
				aggregator.SpecSuiteDidEnd(suiteSummary2)
			})

			It("should be silent", func() {
				Consistently(func() interface{} { return stenographer.Calls() }).Should(BeEmpty())
			})

			It("should not notify the channel", func() {
				Ω(result).Should(BeEmpty())
			})
		})

		Context("once all of the parallel-suites end", func() {
			BeforeEach(func() {
				time.Sleep(200 * time.Millisecond)

				suiteSummary1.SuiteSucceeded = true
				suiteSummary1.NumberOfPassedSpecs = 15
				suiteSummary1.NumberOfFailedSpecs = 0
				suiteSummary1.NumberOfFlakedSpecs = 3
				suiteSummary2.SuiteSucceeded = false
				suiteSummary2.NumberOfPassedSpecs = 5
				suiteSummary2.NumberOfFailedSpecs = 3
				suiteSummary2.NumberOfFlakedSpecs = 4

				aggregator.SpecSuiteDidEnd(suiteSummary2)
				aggregator.SpecSuiteDidEnd(suiteSummary1)
				Eventually(func() interface{} {
					return stenographer.Calls()
				}).Should(HaveLen(2))
			})

			It("should announce the end of the suite", func() {
				compositeSummary := stenographer.Calls()[1].Args[0].(*types.SuiteSummary)

				Ω(compositeSummary.SuiteSucceeded).Should(BeFalse())
				Ω(compositeSummary.NumberOfSpecsThatWillBeRun).Should(Equal(23))
				Ω(compositeSummary.NumberOfTotalSpecs).Should(Equal(30))
				Ω(compositeSummary.NumberOfPassedSpecs).Should(Equal(20))
				Ω(compositeSummary.NumberOfFailedSpecs).Should(Equal(3))
				Ω(compositeSummary.NumberOfPendingSpecs).Should(Equal(3))
				Ω(compositeSummary.NumberOfSkippedSpecs).Should(Equal(4))
				Ω(compositeSummary.NumberOfFlakedSpecs).Should(Equal(7))
				Ω(compositeSummary.RunTime.Seconds()).Should(BeNumerically(">", 0.2))
			})
		})

		Context("when all the parallel-suites pass", func() {
			BeforeEach(func() {
				suiteSummary1.SuiteSucceeded = true
				suiteSummary2.SuiteSucceeded = true

				aggregator.SpecSuiteDidEnd(suiteSummary2)
				aggregator.SpecSuiteDidEnd(suiteSummary1)
				Eventually(func() interface{} {
					return stenographer.Calls()
				}).Should(HaveLen(2))
			})

			It("should report success", func() {
				compositeSummary := stenographer.Calls()[1].Args[0].(*types.SuiteSummary)

				Ω(compositeSummary.SuiteSucceeded).Should(BeTrue())
			})

			It("should notify the channel that it succeded", func(done Done) {
				Ω(<-result).Should(BeTrue())
				close(done)
			})
		})

		Context("when one of the parallel-suites fails", func() {
			BeforeEach(func() {
				suiteSummary1.SuiteSucceeded = true
				suiteSummary2.SuiteSucceeded = false

				aggregator.SpecSuiteDidEnd(suiteSummary2)
				aggregator.SpecSuiteDidEnd(suiteSummary1)
				Eventually(func() interface{} {
					return stenographer.Calls()
				}).Should(HaveLen(2))
			})

			It("should report failure", func() {
				compositeSummary := stenographer.Calls()[1].Args[0].(*types.SuiteSummary)

				Ω(compositeSummary.SuiteSucceeded).Should(BeFalse())
			})

			It("should notify the channel that it failed", func(done Done) {
				Ω(<-result).Should(BeFalse())
				close(done)
			})
		})
	})
})
