package handlers

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gorilla/mux"
	"github.com/seaweedfs/seaweedfs/weed/admin/dash"
	"github.com/seaweedfs/seaweedfs/weed/admin/view/app"
	"github.com/seaweedfs/seaweedfs/weed/admin/view/layout"
	"github.com/seaweedfs/seaweedfs/weed/glog"
	"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
)

// PolicyHandlers contains all the HTTP handlers for policy management
type PolicyHandlers struct {
	adminServer *dash.AdminServer
}

// NewPolicyHandlers creates a new instance of PolicyHandlers
func NewPolicyHandlers(adminServer *dash.AdminServer) *PolicyHandlers {
	return &PolicyHandlers{
		adminServer: adminServer,
	}
}

// ShowPolicies renders the policies management page
func (h *PolicyHandlers) ShowPolicies(w http.ResponseWriter, r *http.Request) {
	// Get policies data from the server
	policiesData := h.getPoliciesData(r)

	// Render HTML template
	w.Header().Set("Content-Type", "text/html")
	policiesComponent := app.Policies(policiesData)
	viewCtx := layout.NewViewContext(r, dash.UsernameFromContext(r.Context()), dash.CSRFTokenFromContext(r.Context()))
	layoutComponent := layout.Layout(viewCtx, policiesComponent)
	if err := layoutComponent.Render(r.Context(), w); err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to render template: "+err.Error())
		return
	}
}

// GetPolicies returns the list of policies as JSON
func (h *PolicyHandlers) GetPolicies(w http.ResponseWriter, r *http.Request) {
	policies, err := h.adminServer.GetPolicies()
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to get policies: "+err.Error())
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"policies": policies})
}

// GetPrincipalSuggestions returns candidate ARNs (existing users and IAM
// roles) for the policy editor's Principal/NotPrincipal autocomplete.
func (h *PolicyHandlers) GetPrincipalSuggestions(w http.ResponseWriter, r *http.Request) {
	suggestions, err := h.adminServer.GetPrincipalSuggestions(r.Context())
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to get principal suggestions: "+err.Error())
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"principals": suggestions})
}

// CreatePolicy handles policy creation
func (h *PolicyHandlers) CreatePolicy(w http.ResponseWriter, r *http.Request) {
	var req dash.CreatePolicyRequest
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}

	// Validate policy name
	if req.Name == "" {
		writeJSONError(w, http.StatusBadRequest, "Policy name is required")
		return
	}

	// Check if policy already exists
	existingPolicy, err := h.adminServer.GetPolicy(req.Name)
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to check existing policy: "+err.Error())
		return
	}
	if existingPolicy != nil {
		writeJSONError(w, http.StatusConflict, "Policy with this name already exists")
		return
	}

	// Create the policy
	err = h.adminServer.CreatePolicy(req.Name, req.Document)
	if err != nil {
		glog.Errorf("Failed to create policy %s: %v", req.Name, err)
		writeJSONError(w, http.StatusInternalServerError, "Failed to create policy: "+err.Error())
		return
	}

	writeJSON(w, http.StatusCreated, map[string]interface{}{
		"success": true,
		"message": "Policy created successfully",
		"policy":  req.Name,
	})
}

// GetPolicy returns a specific policy
func (h *PolicyHandlers) GetPolicy(w http.ResponseWriter, r *http.Request) {
	policyName := mux.Vars(r)["name"]
	if policyName == "" {
		writeJSONError(w, http.StatusBadRequest, "Policy name is required")
		return
	}

	policy, err := h.adminServer.GetPolicy(policyName)
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to get policy: "+err.Error())
		return
	}

	if policy == nil {
		writeJSONError(w, http.StatusNotFound, "Policy not found")
		return
	}

	writeJSON(w, http.StatusOK, policy)
}

// UpdatePolicy handles policy updates
func (h *PolicyHandlers) UpdatePolicy(w http.ResponseWriter, r *http.Request) {
	policyName := mux.Vars(r)["name"]
	if policyName == "" {
		writeJSONError(w, http.StatusBadRequest, "Policy name is required")
		return
	}

	var req dash.UpdatePolicyRequest
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}

	// Check if policy exists
	existingPolicy, err := h.adminServer.GetPolicy(policyName)
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to check existing policy: "+err.Error())
		return
	}
	if existingPolicy == nil {
		writeJSONError(w, http.StatusNotFound, "Policy not found")
		return
	}

	// Update the policy
	err = h.adminServer.UpdatePolicy(policyName, req.Document)
	if err != nil {
		glog.Errorf("Failed to update policy %s: %v", policyName, err)
		writeJSONError(w, http.StatusInternalServerError, "Failed to update policy: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"success": true,
		"message": "Policy updated successfully",
		"policy":  policyName,
	})
}

// DeletePolicy handles policy deletion
func (h *PolicyHandlers) DeletePolicy(w http.ResponseWriter, r *http.Request) {
	policyName := mux.Vars(r)["name"]
	if policyName == "" {
		writeJSONError(w, http.StatusBadRequest, "Policy name is required")
		return
	}

	// Check if policy exists
	existingPolicy, err := h.adminServer.GetPolicy(policyName)
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to check existing policy: "+err.Error())
		return
	}
	if existingPolicy == nil {
		writeJSONError(w, http.StatusNotFound, "Policy not found")
		return
	}

	// Delete the policy
	err = h.adminServer.DeletePolicy(policyName)
	if err != nil {
		glog.Errorf("Failed to delete policy %s: %v", policyName, err)
		writeJSONError(w, http.StatusInternalServerError, "Failed to delete policy: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"success": true,
		"message": "Policy deleted successfully",
		"policy":  policyName,
	})
}

// ValidatePolicy validates a policy document without saving it
func (h *PolicyHandlers) ValidatePolicy(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Document policy_engine.PolicyDocument `json:"document"`
	}

	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}

	// Basic validation
	if req.Document.Version == "" {
		writeJSONError(w, http.StatusBadRequest, "Policy version is required")
		return
	}

	if len(req.Document.Statement) == 0 {
		writeJSONError(w, http.StatusBadRequest, "Policy must have at least one statement")
		return
	}

	// Validate each statement
	for i, statement := range req.Document.Statement {
		if statement.Effect != "Allow" && statement.Effect != "Deny" {
			writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("Statement %d: Effect must be 'Allow' or 'Deny'", i+1))
			return
		}

		if len(statement.Action.Strings()) == 0 {
			writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("Statement %d: Action is required", i+1))
			return
		}

		if len(statement.Resource.Strings()) == 0 && len(statement.NotResource.Strings()) == 0 {
			writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("Statement %d: Resource or NotResource is required", i+1))
			return
		}

		if statement.Resource != nil && statement.NotResource != nil {
			writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("Statement %d: cannot specify both Resource and NotResource", i+1))
			return
		}

		if statement.Principal != nil && statement.NotPrincipal != nil {
			writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("Statement %d: cannot specify both Principal and NotPrincipal", i+1))
			return
		}
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"valid":   true,
		"message": "Policy document is valid",
	})
}

// getPoliciesData retrieves policies data from the server
func (h *PolicyHandlers) getPoliciesData(r *http.Request) dash.PoliciesData {
	username := dash.UsernameFromContext(r.Context())
	if username == "" {
		username = "admin"
	}

	// Get policies
	policies, err := h.adminServer.GetPolicies()
	if err != nil {
		glog.Errorf("Failed to get policies: %v", err)
		// Return empty data on error
		return dash.PoliciesData{
			Username:      username,
			Policies:      []dash.IAMPolicy{},
			TotalPolicies: 0,
			LastUpdated:   time.Now(),
		}
	}

	// Ensure policies is never nil
	if policies == nil {
		policies = []dash.IAMPolicy{}
	}

	return dash.PoliciesData{
		Username:      username,
		Policies:      policies,
		TotalPolicies: len(policies),
		LastUpdated:   time.Now(),
	}
}
