package cookies

import (
	"fmt"
	"net/http"
	"time"

	middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("Cookie Tests", func() {
	Context("GetCookieDomain", func() {
		type getCookieDomainTableInput struct {
			host           string
			xForwardedHost string
			cookieDomains  []string
			expectedOutput string
		}

		DescribeTable("should return expected results",
			func(in getCookieDomainTableInput) {
				req, err := http.NewRequest(
					http.MethodGet,
					fmt.Sprintf("https://%s/%s", in.host, cookiePath),
					nil,
				)
				Expect(err).ToNot(HaveOccurred())

				if in.xForwardedHost != "" {
					req.Header.Add("X-Forwarded-Host", in.xForwardedHost)
					req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
						ReverseProxy: true,
					})
				}

				Expect(GetCookieDomain(req, in.cookieDomains)).To(Equal(in.expectedOutput))
			},
			Entry("a single exact match for the Host header", getCookieDomainTableInput{
				host:           "www.cookies.test",
				cookieDomains:  []string{"www.cookies.test"},
				expectedOutput: "www.cookies.test",
			}),
			Entry("a single exact match for the X-Forwarded-Host header", getCookieDomainTableInput{
				host:           "backend.cookies.internal",
				xForwardedHost: "www.cookies.test",
				cookieDomains:  []string{"www.cookies.test"},
				expectedOutput: "www.cookies.test",
			}),
			Entry("a single suffix match for the Host header", getCookieDomainTableInput{
				host:           "www.cookies.test",
				cookieDomains:  []string{".cookies.test"},
				expectedOutput: ".cookies.test",
			}),
			Entry("a single suffix match for the X-Forwarded-Host header", getCookieDomainTableInput{
				host:           "backend.cookies.internal",
				xForwardedHost: "www.cookies.test",
				cookieDomains:  []string{".cookies.test"},
				expectedOutput: ".cookies.test",
			}),
			Entry("the first match is used", getCookieDomainTableInput{
				host:           "www.cookies.test",
				cookieDomains:  []string{"www.cookies.test", ".cookies.test"},
				expectedOutput: "www.cookies.test",
			}),
			Entry("the only match is used", getCookieDomainTableInput{
				host:           "www.cookies.test",
				cookieDomains:  []string{".cookies.wrong", ".cookies.test"},
				expectedOutput: ".cookies.test",
			}),
			Entry("blank is returned for no matches", getCookieDomainTableInput{
				host:           "www.cookies.test",
				cookieDomains:  []string{".cookies.wrong", ".cookies.false"},
				expectedOutput: "",
			}),
		)
	})

	Context("MakeCookieFromOptions", func() {
		type makeCookieFromOptionsTableInput struct {
			host           string
			opts           CookieOptions
			now            time.Time
			expectedOutput int
		}

		validName := "_oauth2_proxy"
		domains := []string{"www.cookies.test"}

		now := time.Now()
		var expectedMaxAge int

		DescribeTable("should return cookies with or without expiration",
			func(in makeCookieFromOptionsTableInput) {
				req, err := http.NewRequest(
					http.MethodGet,
					fmt.Sprintf("https://%s/%s", in.host, cookiePath),
					nil,
				)
				Expect(err).ToNot(HaveOccurred())

				Expect(MakeCookieFromOptions(req, &in.opts).MaxAge).To(Equal(in.expectedOutput))
			},
			Entry("persistent cookie", makeCookieFromOptionsTableInput{
				host: "www.cookies.test",
				opts: CookieOptions{
					Name:       validName,
					Value:      "1",
					Domains:    domains,
					Expiration: 15 * time.Minute,
					SameSite:   "",
					Path:       "",
					HTTPOnly:   false,
					Secure:     true,
				},
				now:            now,
				expectedOutput: int((15 * time.Minute).Seconds()),
			}),
			Entry("persistent cookie to be cleared", makeCookieFromOptionsTableInput{
				host: "www.cookies.test",
				opts: CookieOptions{
					Name:       validName,
					Value:      "1",
					Domains:    domains,
					Expiration: time.Hour * -1,
					SameSite:   "",
					Path:       "",
					HTTPOnly:   false,
					Secure:     true,
				},
				now:            now,
				expectedOutput: -1,
			}),
			Entry("session cookie", makeCookieFromOptionsTableInput{
				host: "www.cookies.test",
				opts: CookieOptions{
					Name:       validName,
					Value:      "1",
					Domains:    domains,
					Expiration: 0,
					SameSite:   "",
					Path:       "",
					HTTPOnly:   false,
					Secure:     true,
				},
				now:            now,
				expectedOutput: expectedMaxAge,
			}),
		)
	})
})
