package internal_integration_test

import (
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"

	"github.com/onsi/ginkgo/v2/internal/interrupt_handler"
	. "github.com/onsi/ginkgo/v2/internal/test_helpers"
	"github.com/onsi/ginkgo/v2/types"
)

var _ = Describe("handling test aborts", func() {
	Describe("when BeforeSuite aborts", func() {
		BeforeEach(func() {
			success, _ := RunFixture("abort beforesuite", func() {
				BeforeSuite(rt.T("before-suite", func() {
					writer.Write([]byte("before-suite"))
					Abort("abort", cl)
				}))
				It("A", rt.T("A"))
				It("B", rt.T("B"))
				AfterSuite(rt.T("after-suite"))
			})
			Ω(success).Should(BeFalse())
		})

		It("reports a suite failure", func() {
			Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(2), NSkipped(0)))
		})

		It("reports a failure for the BeforeSuite", func() {
			Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HaveAborted("abort", cl, CapturedGinkgoWriterOutput("before-suite")))
			Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HavePassed())
		})

		It("does not run any of the Its", func() {
			Ω(rt).ShouldNot(HaveRun("A"))
			Ω(rt).ShouldNot(HaveRun("B"))
		})

		It("does run the AfterSuite", func() {
			Ω(rt).Should(HaveTracked("before-suite", "after-suite"))
		})
	})

	Describe("when AfterSuite aborts", func() {
		BeforeEach(func() {
			success, _ := RunFixture("abort aftersuite", func() {
				BeforeSuite(rt.T("before-suite"))
				Describe("top-level", func() {
					It("A", rt.T("A"))
					It("B", rt.T("B"))
				})
				AfterSuite(rt.T("after-suite", func() {
					writer.Write([]byte("after-suite"))
					Abort("abort", cl)
				}))
			})
			Ω(success).Should(BeFalse())
		})

		It("reports a suite failure", func() {
			Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(2), NPassed(2)))
		})

		It("runs and reports on all the tests and reports a failure for the AfterSuite", func() {
			Ω(rt).Should(HaveTracked("before-suite", "A", "B", "after-suite"))
			Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeBeforeSuite)).Should(HavePassed())
			Ω(reporter.Did.Find("A")).Should(HavePassed())
			Ω(reporter.Did.Find("B")).Should(HavePassed())
			Ω(reporter.Did.FindByLeafNodeType(types.NodeTypeAfterSuite)).Should(HaveAborted("abort", cl, CapturedGinkgoWriterOutput("after-suite")))
		})
	})

	Describe("individual test aborts", func() {
		Describe("when an It aborts", func() {
			BeforeEach(func() {
				success, _ := RunFixture("failed it", func() {
					BeforeSuite(rt.T("before-suite"))
					Describe("top-level", func() {
						It("A", rt.T("A", func() {
							writer.Write([]byte("running A"))
						}))
						It("B", rt.T("B", func() {
							writer.Write([]byte("running B"))
							Abort("abort", cl)
						}))
						It("C", rt.T("C"))
						It("D", rt.T("D"))
					})
					AfterEach(rt.T("after-each"))
					AfterSuite(rt.T("after-suite"))
				})
				Ω(success).Should(BeFalse())
			})

			It("reports a suite failure", func() {
				Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(4), NPassed(1), NFailed(1), NSkipped(2)))
			})

			It("does not run subsequent Its, the AfterEach, and the AfterSuite", func() {
				Ω(rt).Should(HaveTracked("before-suite", "A", "after-each", "B", "after-each", "after-suite"))
			})

			It("reports the It's abort and subsequent tests as skipped", func() {
				Ω(reporter.Did.Find("A")).Should(HavePassed(CapturedGinkgoWriterOutput("running A")))
				Ω(reporter.Did.Find("B")).Should(HaveAborted("abort", cl, CapturedGinkgoWriterOutput("running B")))
				Ω(reporter.Did.Find("C")).Should(HaveBeenSkipped())
				Ω(reporter.Did.Find("D")).Should(HaveBeenSkipped())
			})

			It("sets up the failure node location correctly", func() {
				report := reporter.Did.Find("B")
				Ω(report.Failure.FailureNodeContext).Should(Equal(types.FailureNodeIsLeafNode))
				Ω(report.Failure.FailureNodeType).Should(Equal(types.NodeTypeIt))
				Ω(report.Failure.FailureNodeLocation).Should(Equal(report.LeafNodeLocation))
			})
		})
	})

	Describe("when a test fails then an AfterEach aborts", func() {
		BeforeEach(func() {
			success, _ := RunFixture("failed it then after-each aborts", func() {
				BeforeSuite(rt.T("before-suite"))
				Describe("top-level", func() {
					It("A", rt.T("A"))
					It("B", rt.T("B", func() {
						writer.Write([]byte("running B"))
						F("fail")
					}))
					It("C", rt.T("C"))
					It("D", rt.T("D"))
				})
				ReportAfterEach(func(report SpecReport) {
					rt.Run("report-after-each")
					if report.State.Is(types.SpecStateFailed) {
						Abort("abort", cl)
					}
				})
				AfterSuite(rt.T("after-suite"))
			})
			Ω(success).Should(BeFalse())
		})

		It("reports a suite failure", func() {
			Ω(reporter.End).Should(BeASuiteSummary(false, NSpecs(4), NPassed(1), NFailed(1), NSkipped(2)))
		})

		It("does not run subsequent Its, the AfterEach, and the AfterSuite", func() {
			Ω(rt).Should(HaveTracked("before-suite", "A", "report-after-each", "B", "report-after-each", "report-after-each", "report-after-each", "after-suite"))
		})

		It("reports a failure and then aborts the rest of the suite", func() {
			Ω(reporter.Did.Find("A")).Should(HavePassed())
			Ω(reporter.Did.Find("B")).Should(HaveAborted("abort", cl, CapturedGinkgoWriterOutput("running B")))
			Ω(reporter.Did.Find("C")).Should(HaveBeenSkipped())
			Ω(reporter.Did.Find("D")).Should(HaveBeenSkipped())
		})
	})

	Describe("when running in parallel and a test aborts", func() {
		var c chan any
		BeforeEach(func() {
			SetUpForParallel(2)
			c = make(chan any)
		})

		It("notifies the server of the abort", func() {
			Ω(client.ShouldAbort()).Should(BeFalse())
			success := RunFixtureInParallel("aborting in parallel", func(_ int) {
				It("A", func() {
					<-c
					Abort("abort")
				})

				It("B", func(ctx SpecContext) {
					close(c)
					select {
					case <-ctx.Done():
						rt.Run("dc-done")
					case <-time.After(interrupt_handler.ABORT_POLLING_INTERVAL * 2):
						rt.Run("dc-after")
					}
				})
			})
			Ω(success).Should(BeFalse())
			Ω(client.ShouldAbort()).Should(BeTrue())

			Ω(rt).Should(HaveTracked("dc-done")) //not dc-after
			Ω(reporter.Did.Find("A")).Should(HaveAborted("abort"))
			Ω(reporter.Did.Find("B")).Should(HaveBeenInterrupted(interrupt_handler.InterruptCauseAbortByOtherProcess))
		})

		It("does not interrupt cleanup nodes", func() {
			success := RunFixtureInParallel("aborting in parallel", func(_ int) {
				It("A", func() {
					<-c
					Abort("abort")
				})

				Context("B", func() {
					It("B", func() {
					})

					AfterEach(func(ctx SpecContext) {
						close(c)
						select {
						case <-ctx.Done():
							rt.Run("dc-done")
						case <-time.After(interrupt_handler.ABORT_POLLING_INTERVAL * 2):
							rt.Run("dc-after")
						}
					})
				})
			})
			Ω(success).Should(BeFalse())

			Ω(rt).Should(HaveTracked("dc-after")) //not dc-done
			Ω(reporter.Did.Find("A")).Should(HaveAborted("abort"))
			Ω(reporter.Did.Find("B")).Should(HavePassed())
		})

		It("does not start serial nodes if an abort occurs", func() {
			success := RunFixtureInParallel("aborting in parallel", func(proc int) {
				It("A", func() {
					time.Sleep(time.Millisecond * 50)
					if proc == 2 {
						rt.Run("aborting")
						Abort("abort")
					}
				})

				It("B", func() {
					time.Sleep(time.Millisecond * 50)
					if proc == 2 {
						rt.Run("aborting")
						Abort("abort")
					}
				})

				It("C", Serial, func() {
					rt.Run("C")
				})
			})
			Ω(success).Should(BeFalse())
			Ω(rt).Should(HaveTracked("aborting")) //just one aborting and we don't see C
		}, MustPassRepeatedly(10))

	})
})
