package auth

import (
	"net/http"
	"strings"

	"github.com/TaruLab/keluargamart-backend/pkg/httpresponse"
	"github.com/TaruLab/keluargamart-backend/pkg/middleware"
)

// Middleware authenticates Bearer tokens and puts the user + token id on ctx.
// Optional extra middlewares (e.g. ScopeMiddleware) run AFTER authentication,
// so they can read the authenticated user from the request context.
func Middleware(svc *Service, extra ...func(http.Handler) http.Handler) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		// Build the post-authentication chain: extras wrap `next`, so their
		// execution order is extra[0] → extra[1] → … → next, all AFTER auth.
		var postAuth http.Handler = next
		for i := len(extra) - 1; i >= 0; i-- {
			postAuth = extra[i](postAuth)
		}
		var h http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			requestID := middleware.RequestIDFrom(r.Context())
			bearer := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
			if bearer == "" || bearer == r.Header.Get("Authorization") {
				httpresponse.Error(w, http.StatusUnauthorized, requestID,
					httpresponse.CodeAuthentication, "Unauthenticated", nil)
				return
			}
			user, tokenID, err := svc.Authenticate(r.Context(), bearer)
			if err != nil {
				switch err {
				case ErrAccountDisabled:
					httpresponse.Error(w, http.StatusForbidden, requestID,
						httpresponse.CodeAccountDisabled, "Akun Anda telah dinonaktifkan", nil)
				default:
					httpresponse.Error(w, http.StatusUnauthorized, requestID,
						httpresponse.CodeAuthentication, "Unauthenticated", nil)
				}
				return
			}
			// Run extras (scope loading etc.) with the user already on ctx.
			postAuth.ServeHTTP(w, r.WithContext(ToContext(r.Context(), user, tokenID)))
		})
		return h
	}
}

// OptionalMiddleware authenticates a Bearer token when one is present and
// leaves the request anonymous otherwise. It exists for surfaces that must stay
// public for anonymous users but still need the caller's identity for an
// ownership check on part of their content — GET /uploads/{key} serves public
// product images and private payment proofs from one route.
//
// A token that is present but invalid/expired/disabled is a 401, never a silent
// downgrade to anonymous.
func OptionalMiddleware(svc *Service) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			bearer := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
			if bearer == "" || bearer == r.Header.Get("Authorization") {
				next.ServeHTTP(w, r) // anonymous
				return
			}
			user, tokenID, err := svc.Authenticate(r.Context(), bearer)
			if err != nil {
				switch err {
				case ErrAccountDisabled:
					httpresponse.Error(w, http.StatusForbidden, middleware.RequestIDFrom(r.Context()),
						httpresponse.CodeAccountDisabled, "Akun Anda telah dinonaktifkan", nil)
				default:
					httpresponse.Error(w, http.StatusUnauthorized, middleware.RequestIDFrom(r.Context()),
						httpresponse.CodeAuthentication, "Unauthenticated", nil)
				}
				return
			}
			next.ServeHTTP(w, r.WithContext(ToContext(r.Context(), user, tokenID)))
		})
	}
}

// RequirePermission enforces the effective permission set (business-rules §7.3).
// admin bypasses everything inside Can().
func RequirePermission(perm string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			u := UserFrom(r.Context())
			if u == nil {
				httpresponse.Error(w, http.StatusUnauthorized, middleware.RequestIDFrom(r.Context()),
					httpresponse.CodeAuthentication, "Unauthenticated", nil)
				return
			}
			if !u.Can(perm) {
				httpresponse.Error(w, http.StatusForbidden, middleware.RequestIDFrom(r.Context()),
					httpresponse.CodeForbidden, "Forbidden", nil)
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}

// RequireAnyPermission allows access if the user holds at least one of the specified permissions.
// admin and owner bypass everything inside Can().
func RequireAnyPermission(perms ...string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			u := UserFrom(r.Context())
			if u == nil {
				httpresponse.Error(w, http.StatusUnauthorized, middleware.RequestIDFrom(r.Context()),
					httpresponse.CodeAuthentication, "Unauthenticated", nil)
				return
			}
			for _, p := range perms {
				if u.Can(p) {
					next.ServeHTTP(w, r)
					return
				}
			}
			httpresponse.Error(w, http.StatusForbidden, middleware.RequestIDFrom(r.Context()),
				httpresponse.CodeForbidden, "Forbidden", nil)
		})
	}
}

// RequireRole checks if the authenticated user holds at least one of the specified roles.
func RequireRole(roles ...Role) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			u := UserFrom(r.Context())
			if u == nil {
				httpresponse.Error(w, http.StatusUnauthorized, middleware.RequestIDFrom(r.Context()),
					httpresponse.CodeAuthentication, "Unauthenticated", nil)
				return
			}
			for _, role := range roles {
				if u.Role == role {
					next.ServeHTTP(w, r)
					return
				}
			}
			httpresponse.Error(w, http.StatusForbidden, middleware.RequestIDFrom(r.Context()),
				httpresponse.CodeForbidden, "Forbidden: role unauthorized", nil)
		})
	}
}

// RequireAdmin restricts route access to RoleAdmin and RoleOwner.
func RequireAdmin() func(http.Handler) http.Handler {
	return RequireRole(RoleAdmin, RoleOwner)
}

// RequireApp enforces the application-access axis (authz audit 2026-08-25):
// an identity may only enter the application(s) its derived ApplicationAccess
// permits. AppPlatform (admin/owner) may enter any application, preserving
// the legacy unrestricted scope; every other identity is confined to its own
// application context. This is the boundary that stops a B2B customer from
// reaching ERP/POS and stops an ERP role from reaching the B2B admin.
func RequireApp(app ApplicationAccess) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			u := UserFrom(r.Context())
			if u == nil {
				httpresponse.Error(w, http.StatusUnauthorized, middleware.RequestIDFrom(r.Context()),
					httpresponse.CodeAuthentication, "Unauthenticated", nil)
				return
			}
			if !u.CanAccessApp(app) {
				httpresponse.Error(w, http.StatusForbidden, middleware.RequestIDFrom(r.Context()),
					httpresponse.CodeForbidden, "Forbidden: application access required", nil)
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}

// RequireAnyApp is RequireApp for shared surfaces: it admits an identity
// holding ANY of the listed application accesses (authz audit 2026-08-25).
// Used where one endpoint legitimately serves two applications, e.g. the
// store POS checkout consumed by both the POS frontend and ERP's quick-sale
// page. Platform identities still pass; E_COMMERCE customers still cannot.
func RequireAnyApp(apps ...ApplicationAccess) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			u := UserFrom(r.Context())
			if u == nil {
				httpresponse.Error(w, http.StatusUnauthorized, middleware.RequestIDFrom(r.Context()),
					httpresponse.CodeAuthentication, "Unauthenticated", nil)
				return
			}
			for _, app := range apps {
				if u.CanAccessApp(app) {
					next.ServeHTTP(w, r)
					return
				}
			}
			httpresponse.Error(w, http.StatusForbidden, middleware.RequestIDFrom(r.Context()),
				httpresponse.CodeForbidden, "Forbidden: application access required", nil)
		})
	}
}
