package service

import (
	"github.com/tidbcloud/aisupport/services/oncall/db"
	"github.com/tidbcloud/aisupport/services/oncall/models"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

// SessionCookieName is the HttpOnly cookie that carries the opaque session
// token in the browser. Keeping the token in an HttpOnly cookie (instead of
// localStorage) means JavaScript cannot read it, so an XSS bug cannot
// exfiltrate a logged-in session. SameSite=Lax is safe because the SPA and the
// API are served from the same origin.
const SessionCookieName = "oncall_session"

// SessionTTL is the idle lifetime of a session. Combined with sliding renewal
// (see RenewSessionIfNeeded), an actively used session effectively never
// expires, while an idle one expires after this window. Override with
// ONCALL_SESSION_TTL_HOURS.
func SessionTTL() time.Duration {
	if v := strings.TrimSpace(os.Getenv("ONCALL_SESSION_TTL_HOURS")); v != "" {
		if h, err := strconv.Atoi(v); err == nil && h > 0 {
			return time.Duration(h) * time.Hour
		}
	}
	return 7 * 24 * time.Hour
}

// cookieIsSecure marks the cookie Secure only when the request actually arrived
// over HTTPS. The dev cluster terminates at an HTTP ALB, so we must not force
// Secure there (the browser would drop the cookie); prod forwards
// X-Forwarded-Proto=https and gets a Secure cookie.
func cookieIsSecure(c *gin.Context) bool {
	if c.Request.TLS != nil {
		return true
	}
	return strings.EqualFold(strings.TrimSpace(c.GetHeader("X-Forwarded-Proto")), "https")
}

// SetSessionCookie writes the session token as an HttpOnly, SameSite=Lax cookie.
func SetSessionCookie(c *gin.Context, token string) {
	c.SetSameSite(http.SameSiteLaxMode)
	c.SetCookie(SessionCookieName, token, int(SessionTTL().Seconds()), "/", "", cookieIsSecure(c), true)
}

// ClearSessionCookie removes the session cookie (used on logout).
func ClearSessionCookie(c *gin.Context) {
	c.SetSameSite(http.SameSiteLaxMode)
	c.SetCookie(SessionCookieName, "", -1, "/", "", cookieIsSecure(c), true)
}

// RenewSessionIfNeeded implements sliding expiration. Once a session passes the
// halfway point of its TTL, an authenticated request pushes its expiry back to a
// full TTL and refreshes the cookie. Frequent users therefore stay logged in
// without ever seeing a login screen, while idle sessions still lapse.
func RenewSessionIfNeeded(c *gin.Context, session *models.UserSession) {
	ttl := SessionTTL()
	if time.Until(session.ExpiresAt) >= ttl/2 {
		return
	}
	session.ExpiresAt = time.Now().Add(ttl)
	if err := db.DB.Model(session).Update("expires_at", session.ExpiresAt).Error; err == nil {
		SetSessionCookie(c, session.Token)
	}
}
