package main

import (
	"context"
	"encoding/json"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

var jwtSecret = func() []byte {
	if s := os.Getenv("IC3_JWT_SECRET"); s != "" {
		return []byte(s)
	}
	return []byte("ic3-dashboard-secret-2026-change-in-prod")
}()

type Claims struct {
	Username string `json:"username"`
	Role     string `json:"role"`
	UserID   int64  `json:"user_id"`
	jwt.RegisteredClaims
}

type ctxKey string

const claimsCtxKey ctxKey = "claims"

// claimsFrom extracts the JWT claims stored by authMiddleware.
func claimsFrom(r *http.Request) *Claims {
	c, _ := r.Context().Value(claimsCtxKey).(*Claims)
	return c
}

// loginHandler authenticates against the users table (bcrypt).
// Falls back to the hard-coded store when DB is unavailable.
func loginHandler(db *DB) http.HandlerFunc {
	// fallback in-memory store (used only when DB is down)
	type entry struct{ password, role string }
	fallback := map[string]entry{
		"admin":    {"admin123", "admin"},
		"operator": {"op123", "operator"},
		"viewer":   {"view123", "viewer"},
	}

	return func(w http.ResponseWriter, r *http.Request) {
		var req struct {
			Username string `json:"username"`
			Password string `json:"password"`
		}
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, `{"error":"invalid request"}`, 400)
			return
		}
		req.Username = strings.TrimSpace(req.Username)

		var (
			role   string
			userID int64
		)

		if db != nil {
			ctx, cancel := context.WithTimeout(r.Context(), 4*time.Second)
			defer cancel()

			u, err := db.verifyUser(ctx, req.Username, req.Password)
			if err != nil {
				w.WriteHeader(401)
				json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
				return
			}
			role   = u.Role
			userID = u.ID
			go db.updateLastLogin(context.Background(), req.Username)
		} else {
			// DB unavailable — use fallback
			e, ok := fallback[req.Username]
			if !ok || e.password != req.Password {
				w.WriteHeader(401)
				json.NewEncoder(w).Encode(map[string]string{"error": "invalid credentials"})
				return
			}
			role = e.role
		}

		claims := Claims{
			Username: req.Username,
			Role:     role,
			UserID:   userID,
			RegisteredClaims: jwt.RegisteredClaims{
				Subject:   req.Username,
				ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
				IssuedAt:  jwt.NewNumericDate(time.Now()),
			},
		}
		token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(jwtSecret)
		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": "token error"})
			return
		}
		json.NewEncoder(w).Encode(map[string]any{
			"token":    token,
			"role":     role,
			"username": req.Username,
			"user_id":  userID,
		})
	}
}

// authMiddleware validates the Bearer token and stores claims in context.
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodOptions {
			next(w, r)
			return
		}
		if os.Getenv("IC3_SKIP_AUTH") == "true" {
			next(w, r)
			return
		}
		auth := r.Header.Get("Authorization")
		if !strings.HasPrefix(auth, "Bearer ") {
			w.WriteHeader(401)
			json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"})
			return
		}
		claims := &Claims{}
		_, err := jwt.ParseWithClaims(
			strings.TrimPrefix(auth, "Bearer "),
			claims,
			func(t *jwt.Token) (any, error) { return jwtSecret, nil },
		)
		if err != nil {
			w.WriteHeader(401)
			json.NewEncoder(w).Encode(map[string]string{"error": "invalid token"})
			return
		}
		ctx := context.WithValue(r.Context(), claimsCtxKey, claims)
		next(w, r.WithContext(ctx))
	}
}

// adminOnly wraps a handler so only admin-role tokens can reach it.
func adminOnly(next http.HandlerFunc) http.HandlerFunc {
	return authMiddleware(func(w http.ResponseWriter, r *http.Request) {
		c := claimsFrom(r)
		if c == nil || c.Role != "admin" {
			w.WriteHeader(403)
			json.NewEncoder(w).Encode(map[string]string{"error": "admin access required"})
			return
		}
		next(w, r)
	})
}
