package authentik

import (
	"net/http"
	"strings"
	"time"
)

const authentikCookiePath = "/auth/"

// SerializedCookie stores an HTTP cookie for flow session persistence.
type SerializedCookie struct {
	Name     string        `json:"name"`
	Value    string        `json:"value"`
	Path     string        `json:"path,omitempty"`
	Domain   string        `json:"domain,omitempty"`
	MaxAge   int           `json:"maxAge,omitempty"`
	Expires  time.Time     `json:"expires,omitempty"`
	Secure   bool          `json:"secure,omitempty"`
	HTTPOnly bool          `json:"httpOnly,omitempty"`
	SameSite http.SameSite `json:"-"`
	SameSiteName string    `json:"sameSite,omitempty"`
}

func (fe *FlowExecutor) ExportCookies() []SerializedCookie {
	if fe.client.Jar == nil {
		return nil
	}
	raw := fe.client.Jar.Cookies(fe.cookieURL())
	out := make([]SerializedCookie, 0, len(raw))
	for _, c := range raw {
		out = append(out, serializeCookie(c))
	}
	return out
}

func (fe *FlowExecutor) ImportCookies(stored []SerializedCookie) {
	if fe.client.Jar == nil || len(stored) == 0 {
		return
	}
	cookies := make([]*http.Cookie, 0, len(stored))
	for _, sc := range stored {
		cookies = append(cookies, deserializeCookie(sc))
	}
	fe.client.Jar.SetCookies(fe.cookieURL(), cookies)
}

func serializeCookie(c *http.Cookie) SerializedCookie {
	sc := SerializedCookie{
		Name:     c.Name,
		Value:    c.Value,
		Path:     c.Path,
		Domain:   c.Domain,
		MaxAge:   c.MaxAge,
		Expires:  c.Expires,
		Secure:   c.Secure,
		HTTPOnly: c.HttpOnly,
		SameSite: c.SameSite,
	}
	switch c.SameSite {
	case http.SameSiteDefaultMode:
		sc.SameSiteName = "Default"
	case http.SameSiteLaxMode:
		sc.SameSiteName = "Lax"
	case http.SameSiteStrictMode:
		sc.SameSiteName = "Strict"
	case http.SameSiteNoneMode:
		sc.SameSiteName = "None"
	}
	return sc
}

func deserializeCookie(sc SerializedCookie) *http.Cookie {
	c := &http.Cookie{
		Name:     sc.Name,
		Value:    sc.Value,
		Path:     sc.Path,
		Domain:   sc.Domain,
		MaxAge:   sc.MaxAge,
		Expires:  sc.Expires,
		Secure:   sc.Secure,
		HttpOnly: sc.HTTPOnly,
	}
	switch strings.ToLower(sc.SameSiteName) {
	case "lax":
		c.SameSite = http.SameSiteLaxMode
	case "strict":
		c.SameSite = http.SameSiteStrictMode
	case "none":
		c.SameSite = http.SameSiteNoneMode
	default:
		c.SameSite = http.SameSiteLaxMode
	}
	return c
}

// BrowserAuthentikCookies rewrites stored cookies for the public Authentik path.
func BrowserAuthentikCookies(stored []SerializedCookie) []*http.Cookie {
	out := make([]*http.Cookie, 0, len(stored))
	for _, sc := range stored {
		c := deserializeCookie(sc)
		c.Path = authentikCookiePath
		c.Domain = ""
		out = append(out, c)
	}
	return out
}

// RestoreFlowExecutor rebuilds an executor from persisted cookies.
func RestoreFlowExecutor(baseURL, slug string, stored []SerializedCookie) (*FlowExecutor, error) {
	fe, err := NewFlowExecutor(baseURL, slug)
	if err != nil {
		return nil, err
	}
	fe.ImportCookies(stored)
	return fe, nil
}

// CloneCookieJar copies cookies between executors (tests).
func CloneCookieJar(from, to *FlowExecutor) {
	if from == nil || to == nil {
		return
	}
	to.ImportCookies(from.ExportCookies())
}