package container

import (
	"github.com/containrrr/watchtower/pkg/types"
	dc "github.com/docker/docker/api/types/container"
	"github.com/docker/go-connections/nat"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("the container", func() {
	Describe("VerifyConfiguration", func() {
		When("verifying a container with no image info", func() {
			It("should return an error", func() {
				c := MockContainer(WithPortBindings())
				c.imageInfo = nil
				err := c.VerifyConfiguration()
				Expect(err).To(Equal(errorNoImageInfo))
			})
		})
		When("verifying a container with no container info", func() {
			It("should return an error", func() {
				c := MockContainer(WithPortBindings())
				c.containerInfo = nil
				err := c.VerifyConfiguration()
				Expect(err).To(Equal(errorNoContainerInfo))
			})
		})
		When("verifying a container with no config", func() {
			It("should return an error", func() {
				c := MockContainer(WithPortBindings())
				c.containerInfo.Config = nil
				err := c.VerifyConfiguration()
				Expect(err).To(Equal(errorInvalidConfig))
			})
		})
		When("verifying a container with no host config", func() {
			It("should return an error", func() {
				c := MockContainer(WithPortBindings())
				c.containerInfo.HostConfig = nil
				err := c.VerifyConfiguration()
				Expect(err).To(Equal(errorInvalidConfig))
			})
		})
		When("verifying a container with no port bindings", func() {
			It("should not return an error", func() {
				c := MockContainer(WithPortBindings())
				err := c.VerifyConfiguration()
				Expect(err).ToNot(HaveOccurred())
			})
		})
		When("verifying a container with port bindings, but no exposed ports", func() {
			It("should make the config compatible with updating", func() {
				c := MockContainer(WithPortBindings("80/tcp"))
				c.containerInfo.Config.ExposedPorts = nil
				Expect(c.VerifyConfiguration()).To(Succeed())

				Expect(c.containerInfo.Config.ExposedPorts).ToNot(BeNil())
				Expect(c.containerInfo.Config.ExposedPorts).To(BeEmpty())
			})
		})
		When("verifying a container with port bindings and exposed ports is non-nil", func() {
			It("should return an error", func() {
				c := MockContainer(WithPortBindings("80/tcp"))
				c.containerInfo.Config.ExposedPorts = map[nat.Port]struct{}{"80/tcp": {}}
				err := c.VerifyConfiguration()
				Expect(err).ToNot(HaveOccurred())
			})
		})
	})
	Describe("GetCreateConfig", func() {
		When("container healthcheck config is equal to image config", func() {
			It("should return empty healthcheck values", func() {
				c := MockContainer(WithHealthcheck(dc.HealthConfig{
					Test: []string{"/usr/bin/sleep", "1s"},
				}), WithImageHealthcheck(dc.HealthConfig{
					Test: []string{"/usr/bin/sleep", "1s"},
				}))
				Expect(c.GetCreateConfig().Healthcheck).To(Equal(&dc.HealthConfig{}))

				c = MockContainer(WithHealthcheck(dc.HealthConfig{
					Timeout: 30,
				}), WithImageHealthcheck(dc.HealthConfig{
					Timeout: 30,
				}))
				Expect(c.GetCreateConfig().Healthcheck).To(Equal(&dc.HealthConfig{}))

				c = MockContainer(WithHealthcheck(dc.HealthConfig{
					StartPeriod: 30,
				}), WithImageHealthcheck(dc.HealthConfig{
					StartPeriod: 30,
				}))
				Expect(c.GetCreateConfig().Healthcheck).To(Equal(&dc.HealthConfig{}))

				c = MockContainer(WithHealthcheck(dc.HealthConfig{
					Retries: 30,
				}), WithImageHealthcheck(dc.HealthConfig{
					Retries: 30,
				}))
				Expect(c.GetCreateConfig().Healthcheck).To(Equal(&dc.HealthConfig{}))
			})
		})
		When("container healthcheck config is different to image config", func() {
			It("should return the container healthcheck values", func() {
				c := MockContainer(WithHealthcheck(dc.HealthConfig{
					Test:        []string{"/usr/bin/sleep", "1s"},
					Interval:    30,
					Timeout:     30,
					StartPeriod: 10,
					Retries:     2,
				}), WithImageHealthcheck(dc.HealthConfig{
					Test:        []string{"/usr/bin/sleep", "10s"},
					Interval:    10,
					Timeout:     60,
					StartPeriod: 30,
					Retries:     10,
				}))
				Expect(c.GetCreateConfig().Healthcheck).To(Equal(&dc.HealthConfig{
					Test:        []string{"/usr/bin/sleep", "1s"},
					Interval:    30,
					Timeout:     30,
					StartPeriod: 10,
					Retries:     2,
				}))
			})
		})
		When("container healthcheck config is empty", func() {
			It("should not panic", func() {
				c := MockContainer(WithImageHealthcheck(dc.HealthConfig{
					Test:        []string{"/usr/bin/sleep", "10s"},
					Interval:    10,
					Timeout:     60,
					StartPeriod: 30,
					Retries:     10,
				}))
				Expect(c.GetCreateConfig().Healthcheck).To(BeNil())
			})
		})
		When("container image healthcheck config is empty", func() {
			It("should not panic", func() {
				c := MockContainer(WithHealthcheck(dc.HealthConfig{
					Test:        []string{"/usr/bin/sleep", "1s"},
					Interval:    30,
					Timeout:     30,
					StartPeriod: 10,
					Retries:     2,
				}))
				Expect(c.GetCreateConfig().Healthcheck).To(Equal(&dc.HealthConfig{
					Test:        []string{"/usr/bin/sleep", "1s"},
					Interval:    30,
					Timeout:     30,
					StartPeriod: 10,
					Retries:     2,
				}))
			})
		})
	})
	When("asked for metadata", func() {
		var c *Container
		BeforeEach(func() {
			c = MockContainer(WithLabels(map[string]string{
				"com.centurylinklabs.watchtower.enable": "true",
				"com.centurylinklabs.watchtower":        "true",
			}))
		})
		It("should return its name on calls to .Name()", func() {
			name := c.Name()
			Expect(name).To(Equal("test-containrrr"))
			Expect(name).NotTo(Equal("wrong-name"))
		})
		It("should return its ID on calls to .ID()", func() {
			id := c.ID()

			Expect(id).To(BeEquivalentTo("container_id"))
			Expect(id).NotTo(BeEquivalentTo("wrong-id"))
		})
		It("should return true, true if enabled on calls to .Enabled()", func() {
			enabled, exists := c.Enabled()

			Expect(enabled).To(BeTrue())
			Expect(exists).To(BeTrue())
		})
		It("should return false, true if present but not true on calls to .Enabled()", func() {
			c = MockContainer(WithLabels(map[string]string{"com.centurylinklabs.watchtower.enable": "false"}))
			enabled, exists := c.Enabled()

			Expect(enabled).To(BeFalse())
			Expect(exists).To(BeTrue())
		})
		It("should return false, false if not present on calls to .Enabled()", func() {
			c = MockContainer(WithLabels(map[string]string{"lol": "false"}))
			enabled, exists := c.Enabled()

			Expect(enabled).To(BeFalse())
			Expect(exists).To(BeFalse())
		})
		It("should return false, false if present but not parsable .Enabled()", func() {
			c = MockContainer(WithLabels(map[string]string{"com.centurylinklabs.watchtower.enable": "falsy"}))
			enabled, exists := c.Enabled()

			Expect(enabled).To(BeFalse())
			Expect(exists).To(BeFalse())
		})
		When("checking if its a watchtower instance", func() {
			It("should return true if the label is set to true", func() {
				isWatchtower := c.IsWatchtower()
				Expect(isWatchtower).To(BeTrue())
			})
			It("should return false if the label is present but set to false", func() {
				c = MockContainer(WithLabels(map[string]string{"com.centurylinklabs.watchtower": "false"}))
				isWatchtower := c.IsWatchtower()
				Expect(isWatchtower).To(BeFalse())
			})
			It("should return false if the label is not present", func() {
				c = MockContainer(WithLabels(map[string]string{"funny.label": "false"}))
				isWatchtower := c.IsWatchtower()
				Expect(isWatchtower).To(BeFalse())
			})
			It("should return false if there are no labels", func() {
				c = MockContainer(WithLabels(map[string]string{}))
				isWatchtower := c.IsWatchtower()
				Expect(isWatchtower).To(BeFalse())
			})
		})
		When("fetching the custom stop signal", func() {
			It("should return the signal if its set", func() {
				c = MockContainer(WithLabels(map[string]string{
					"com.centurylinklabs.watchtower.stop-signal": "SIGKILL",
				}))
				stopSignal := c.StopSignal()
				Expect(stopSignal).To(Equal("SIGKILL"))
			})
			It("should return an empty string if its not set", func() {
				c = MockContainer(WithLabels(map[string]string{}))
				stopSignal := c.StopSignal()
				Expect(stopSignal).To(Equal(""))
			})
		})
		When("fetching the image name", func() {
			When("the zodiac label is present", func() {
				It("should fetch the image name from it", func() {
					c = MockContainer(WithLabels(map[string]string{
						"com.centurylinklabs.zodiac.original-image": "the-original-image",
					}))
					imageName := c.ImageName()
					Expect(imageName).To(Equal(imageName))
				})
			})
			It("should return the image name", func() {
				name := "image-name:3"
				c = MockContainer(WithImageName(name))
				imageName := c.ImageName()
				Expect(imageName).To(Equal(name))
			})
			It("should assume latest if no tag is supplied", func() {
				name := "image-name"
				c = MockContainer(WithImageName(name))
				imageName := c.ImageName()
				Expect(imageName).To(Equal(name + ":latest"))
			})
		})

		When("fetching container links", func() {
			When("the depends on label is present", func() {
				It("should fetch depending containers from it", func() {
					c = MockContainer(WithLabels(map[string]string{
						"com.centurylinklabs.watchtower.depends-on": "postgres",
					}))
					links := c.Links()
					Expect(links).To(SatisfyAll(ContainElement("/postgres"), HaveLen(1)))
				})
				It("should fetch depending containers if there are many", func() {
					c = MockContainer(WithLabels(map[string]string{
						"com.centurylinklabs.watchtower.depends-on": "postgres,redis",
					}))
					links := c.Links()
					Expect(links).To(SatisfyAll(ContainElement("/postgres"), ContainElement("/redis"), HaveLen(2)))
				})
				It("should only add slashes to names when they are missing", func() {
					c = MockContainer(WithLabels(map[string]string{
						"com.centurylinklabs.watchtower.depends-on": "/postgres,redis",
					}))
					links := c.Links()
					Expect(links).To(SatisfyAll(ContainElement("/postgres"), ContainElement("/redis")))
				})
				It("should fetch depending containers if label is blank", func() {
					c = MockContainer(WithLabels(map[string]string{
						"com.centurylinklabs.watchtower.depends-on": "",
					}))
					links := c.Links()
					Expect(links).To(HaveLen(0))
				})
			})
			When("the depends on label is not present", func() {
				It("should fetch depending containers from host config links", func() {
					c = MockContainer(WithLinks([]string{
						"redis:test-containrrr",
						"postgres:test-containrrr",
					}))
					links := c.Links()
					Expect(links).To(SatisfyAll(ContainElement("redis"), ContainElement("postgres"), HaveLen(2)))
				})
			})
		})

		When("checking no-pull label", func() {
			When("no-pull argument is not set", func() {
				When("no-pull label is true", func() {
					c := MockContainer(WithLabels(map[string]string{
						"com.centurylinklabs.watchtower.no-pull": "true",
					}))
					It("should return true", func() {
						Expect(c.IsNoPull(types.UpdateParams{})).To(Equal(true))
					})
				})
				When("no-pull label is false", func() {
					c := MockContainer(WithLabels(map[string]string{
						"com.centurylinklabs.watchtower.no-pull": "false",
					}))
					It("should return false", func() {
						Expect(c.IsNoPull(types.UpdateParams{})).To(Equal(false))
					})
				})
				When("no-pull label is set to an invalid value", func() {
					c := MockContainer(WithLabels(map[string]string{
						"com.centurylinklabs.watchtower.no-pull": "maybe",
					}))
					It("should return false", func() {
						Expect(c.IsNoPull(types.UpdateParams{})).To(Equal(false))
					})
				})
				When("no-pull label is unset", func() {
					c = MockContainer(WithLabels(map[string]string{}))
					It("should return false", func() {
						Expect(c.IsNoPull(types.UpdateParams{})).To(Equal(false))
					})
				})
			})
			When("no-pull argument is set to true", func() {
				When("no-pull label is true", func() {
					c := MockContainer(WithLabels(map[string]string{
						"com.centurylinklabs.watchtower.no-pull": "true",
					}))
					It("should return true", func() {
						Expect(c.IsNoPull(types.UpdateParams{NoPull: true})).To(Equal(true))
					})
				})
				When("no-pull label is false", func() {
					c := MockContainer(WithLabels(map[string]string{
						"com.centurylinklabs.watchtower.no-pull": "false",
					}))
					It("should return true", func() {
						Expect(c.IsNoPull(types.UpdateParams{NoPull: true})).To(Equal(true))
					})
				})
				When("label-take-precedence argument is set to true", func() {
					When("no-pull label is true", func() {
						c := MockContainer(WithLabels(map[string]string{
							"com.centurylinklabs.watchtower.no-pull": "true",
						}))
						It("should return true", func() {
							Expect(c.IsNoPull(types.UpdateParams{LabelPrecedence: true, NoPull: true})).To(Equal(true))
						})
					})
					When("no-pull label is false", func() {
						c := MockContainer(WithLabels(map[string]string{
							"com.centurylinklabs.watchtower.no-pull": "false",
						}))
						It("should return false", func() {
							Expect(c.IsNoPull(types.UpdateParams{LabelPrecedence: true, NoPull: true})).To(Equal(false))
						})
					})
				})
			})
		})

		When("there is a pre or post update timeout", func() {
			It("should return minute values", func() {
				c = MockContainer(WithLabels(map[string]string{
					"com.centurylinklabs.watchtower.lifecycle.pre-update-timeout":  "3",
					"com.centurylinklabs.watchtower.lifecycle.post-update-timeout": "5",
				}))
				preTimeout := c.PreUpdateTimeout()
				Expect(preTimeout).To(Equal(3))
				postTimeout := c.PostUpdateTimeout()
				Expect(postTimeout).To(Equal(5))
			})
		})

	})
})
