package api

import (
	"context"
	"database/sql"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/rycroftapparel/workpulse-api/internal/authjwt"
	"github.com/rycroftapparel/workpulse-api/internal/config"
	"github.com/rycroftapparel/workpulse-api/internal/httpapi"
	"github.com/rycroftapparel/workpulse-api/internal/realtime"
)

type Server struct {
	DB  *sql.DB
	Cfg *config.Config
	Hub *realtime.Hub
}

func (s *Server) ctx(c *gin.Context) (context.Context, context.CancelFunc) {
	return context.WithTimeout(c.Request.Context(), s.Cfg.QueryTimeout)
}

// authCtx is a tighter deadline for login/register/refresh so a stuck DB does not block until the global query timeout.
func (s *Server) authCtx(c *gin.Context) (context.Context, context.CancelFunc) {
	return context.WithTimeout(c.Request.Context(), s.Cfg.AuthQueryTimeout)
}

func (s *Server) accessTokenString(c *gin.Context) string {
	h := c.GetHeader("Authorization")
	const p = "Bearer "
	if strings.HasPrefix(h, p) {
		return strings.TrimSpace(strings.TrimPrefix(h, p))
	}
	if tok := accessTokenFromCookie(c); tok != "" {
		return tok
	}
	return ""
}

func (s *Server) accessClaims(c *gin.Context) (*authjwt.Claims, bool) {
	tok := s.accessTokenString(c)
	if tok == "" {
		return nil, false
	}
	cl, err := authjwt.ParseAccess(s.Cfg.JWTSecret, tok)
	if err != nil {
		return nil, false
	}
	return cl, true
}

func (s *Server) accessUserID(c *gin.Context) (uint64, bool) {
	cl, ok := s.accessClaims(c)
	if !ok {
		return 0, false
	}
	return cl.UserID, true
}

// bearerUserID is an alias for accessUserID (Authorization or access_token cookie).
func (s *Server) bearerUserID(c *gin.Context) (uint64, bool) {
	return s.accessUserID(c)
}

func (s *Server) authMiddleware(c *gin.Context) {
	cl, ok := s.accessClaims(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, httpapi.Fail("unauthorized", "missing or invalid access token"))
		c.Abort()
		return
	}
	c.Set("userID", cl.UserID)
	c.Set("role", cl.Role)

	ctx, cancel := s.ctx(c)
	defer cancel()
	perms, err := s.loadUserPermissions(ctx, cl.UserID, cl.Role)
	if err != nil {
		c.JSON(http.StatusInternalServerError, httpapi.Fail("db", "terjadi gangguan pada server"))
		c.Abort()
		return
	}
	c.Set("permissions", perms)
	c.Next()
}

func (s *Server) requireSuperadmin(c *gin.Context) {
	roleVal, ok := c.Get("role")
	if !ok {
		c.JSON(http.StatusForbidden, httpapi.Fail("forbidden", "superadmin role required"))
		c.Abort()
		return
	}
	r, ok := roleVal.(string)
	if !ok || r != "superadmin" {
		c.JSON(http.StatusForbidden, httpapi.Fail("forbidden", "superadmin role required"))
		c.Abort()
		return
	}
	c.Next()
}

func (s *Server) IsTeamMember(ctx context.Context, userID, teamID uint64) (bool, error) {
	var one int
	err := s.DB.QueryRowContext(ctx, `SELECT 1 FROM team_members WHERE team_id = $1 AND user_id = $2 LIMIT 1`, teamID, userID).Scan(&one)
	if err == sql.ErrNoRows {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return true, nil
}
