package httpserver

import (
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"update_server/internal/apikeys"
	"update_server/internal/db"
)

func (h *handler) adminAPIKeys(w http.ResponseWriter, r *http.Request) {
	keys, err := h.apiKeys.List(r.Context())
	if err != nil {
		http.Error(w, "api key listing failed", http.StatusInternalServerError)
		return
	}

	data := APIKeysPageData{
		PageData: h.basePageData(
			r,
			"API Keys",
			"Protected Admin",
			"API Keys",
			"Generate client credentials, assign action permissions, and control project visibility through project or tag allow and deny rules.",
		),
		APIKeys: keys,
	}
	data.Flash = apiKeyListFlash(r.URL.Query().Get("status"))

	renderPage(w, h.renderer, "api_keys", data)
}

func (h *handler) adminAPIKeyNew(w http.ResponseWriter, r *http.Request) {
	data := APIKeyPageData{
		PageData: h.basePageData(
			r,
			"New API Key",
			"Protected Admin",
			"Create API Key",
			"Generate a secure key, choose the minimum permissions it needs, and define access with one scope mode at a time.",
		),
		Form: APIKeyFormData{
			Action:      "/admin/api-keys",
			SubmitLabel: "Create API key",
			ScopeMode:   db.ScopeModeProjectAllowList,
			CanDownload: true,
		},
	}

	if err := h.populateAPIKeyChoices(r, &data); err != nil {
		http.Error(w, "api key form failed", http.StatusInternalServerError)
		return
	}

	renderPage(w, h.renderer, "api_key_form", data)
}

func (h *handler) adminAPIKeyCreate(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, "invalid api key form", http.StatusBadRequest)
		return
	}

	form, inputErr := apiKeyFormDataFromRequest(r, "/admin/api-keys", "Create API key")
	if inputErr != "" {
		form.Error = inputErr
		h.renderAPIKeyNewPage(w, r, http.StatusBadRequest, form)
		return
	}

	expiresAt, err := parseAPIKeyExpiration(form.ExpiresAt)
	if err != nil {
		form.Error = "Expiration must be empty, YYYY-MM-DD, or RFC3339 UTC."
		h.renderAPIKeyNewPage(w, r, http.StatusBadRequest, form)
		return
	}

	result, err := h.apiKeys.Create(r.Context(), apikeys.CreateParams{
		Name:              form.Name,
		Description:       form.Description,
		ScopeMode:         form.ScopeMode,
		CanDownload:       form.CanDownload,
		CanUpload:         form.CanUpload,
		CanDelete:         form.CanDelete,
		CanManageProjects: form.CanManageProjects,
		ExpiresAt:         expiresAt,
		ProjectIDs:        form.SelectedProjectIDs,
		TagIDs:            form.SelectedTagIDs,
		CreatedByUserID:   h.currentUserID(r),
	})
	if err != nil {
		if userError := apiKeyUserError(err); userError != "" {
			form.Error = userError
			h.renderAPIKeyNewPage(w, r, http.StatusBadRequest, form)
			return
		}

		http.Error(w, "api key creation failed", http.StatusInternalServerError)
		return
	}

	detailForm := APIKeyFormData{
		RevealKey: result.RawKey,
	}

	h.renderAPIKeyDetailPage(w, r, result.APIKey.ID, http.StatusOK, detailForm)
}

func (h *handler) adminAPIKeyDetail(w http.ResponseWriter, r *http.Request) {
	apiKeyID, err := routeID(r, "apiKeyID")
	if err != nil {
		http.NotFound(w, r)
		return
	}

	data, err := h.apiKeyPageData(r, apiKeyID, APIKeyFormData{})
	if err != nil {
		h.renderAPIKeyDetailError(w, r, err)
		return
	}

	data.Flash = apiKeyDetailFlash(r.URL.Query().Get("status"))
	renderPage(w, h.renderer, "api_key_form", *data)
}

func (h *handler) adminAPIKeyUpdate(w http.ResponseWriter, r *http.Request) {
	apiKeyID, err := routeID(r, "apiKeyID")
	if err != nil {
		http.NotFound(w, r)
		return
	}

	if err := r.ParseForm(); err != nil {
		http.Error(w, "invalid api key form", http.StatusBadRequest)
		return
	}

	form, inputErr := apiKeyFormDataFromRequest(r, fmt.Sprintf("/admin/api-keys/%d", apiKeyID), "Save API key")
	if inputErr != "" {
		form.Error = inputErr
		h.renderAPIKeyDetailPage(w, r, apiKeyID, http.StatusBadRequest, form)
		return
	}

	expiresAt, err := parseAPIKeyExpiration(form.ExpiresAt)
	if err != nil {
		form.Error = "Expiration must be empty, YYYY-MM-DD, or RFC3339 UTC."
		h.renderAPIKeyDetailPage(w, r, apiKeyID, http.StatusBadRequest, form)
		return
	}

	_, err = h.apiKeys.Update(r.Context(), apiKeyID, apikeys.UpdateParams{
		Name:              form.Name,
		Description:       form.Description,
		ScopeMode:         form.ScopeMode,
		CanDownload:       form.CanDownload,
		CanUpload:         form.CanUpload,
		CanDelete:         form.CanDelete,
		CanManageProjects: form.CanManageProjects,
		ExpiresAt:         expiresAt,
		ProjectIDs:        form.SelectedProjectIDs,
		TagIDs:            form.SelectedTagIDs,
	})
	if err != nil {
		switch {
		case errors.Is(err, db.ErrNotFound):
			http.NotFound(w, r)
		case apiKeyUserError(err) != "":
			form.Error = apiKeyUserError(err)
			h.renderAPIKeyDetailPage(w, r, apiKeyID, http.StatusBadRequest, form)
		default:
			http.Error(w, "api key update failed", http.StatusInternalServerError)
		}
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/admin/api-keys/%d?status=api-key-updated", apiKeyID), http.StatusSeeOther)
}

func (h *handler) adminAPIKeyToggleActive(w http.ResponseWriter, r *http.Request) {
	apiKeyID, err := routeID(r, "apiKeyID")
	if err != nil {
		http.NotFound(w, r)
		return
	}

	if err := r.ParseForm(); err != nil {
		http.Error(w, "invalid api key lifecycle form", http.StatusBadRequest)
		return
	}

	var (
		isActive   bool
		statusCode string
	)

	switch strings.TrimSpace(r.FormValue("state")) {
	case "activate":
		isActive = true
		statusCode = "api-key-activated"
	case "revoke":
		isActive = false
		statusCode = "api-key-revoked"
	default:
		http.Error(w, "invalid lifecycle action", http.StatusBadRequest)
		return
	}

	if err := h.apiKeys.SetActive(r.Context(), apiKeyID, isActive); err != nil {
		if errors.Is(err, db.ErrNotFound) {
			http.NotFound(w, r)
			return
		}

		http.Error(w, "api key lifecycle update failed", http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/admin/api-keys/%d?status=%s", apiKeyID, statusCode), http.StatusSeeOther)
}

func (h *handler) renderAPIKeyNewPage(w http.ResponseWriter, r *http.Request, status int, form APIKeyFormData) {
	data := APIKeyPageData{
		PageData: h.basePageData(
			r,
			"New API Key",
			"Protected Admin",
			"Create API Key",
			"Generate a secure key, choose the minimum permissions it needs, and define access with one scope mode at a time.",
		),
		Form: form,
	}

	if err := h.populateAPIKeyChoices(r, &data); err != nil {
		http.Error(w, "api key form failed", http.StatusInternalServerError)
		return
	}

	renderPageStatus(w, h.renderer, "api_key_form", status, data)
}

func (h *handler) renderAPIKeyDetailPage(w http.ResponseWriter, r *http.Request, apiKeyID int64, status int, form APIKeyFormData) {
	if form.RevealKey != "" {
		applyNoStoreHeaders(w)
	}

	data, err := h.apiKeyPageData(r, apiKeyID, form)
	if err != nil {
		h.renderAPIKeyDetailError(w, r, err)
		return
	}

	renderPageStatus(w, h.renderer, "api_key_form", status, *data)
}

func (h *handler) renderAPIKeyDetailError(w http.ResponseWriter, r *http.Request, err error) {
	if errors.Is(err, db.ErrNotFound) {
		http.NotFound(w, r)
		return
	}

	http.Error(w, "api key page failed", http.StatusInternalServerError)
}

func (h *handler) apiKeyPageData(r *http.Request, apiKeyID int64, form APIKeyFormData) (*APIKeyPageData, error) {
	apiKey, err := h.apiKeys.GetByID(r.Context(), apiKeyID)
	if err != nil {
		return nil, err
	}

	projectAccess, err := h.apiKeys.ListProjectAccess(r.Context(), apiKeyID)
	if err != nil {
		return nil, err
	}

	tagAccess, err := h.apiKeys.ListTagAccess(r.Context(), apiKeyID)
	if err != nil {
		return nil, err
	}

	accessibleProjects, err := h.apiKeys.ListAccessibleProjects(r.Context(), *apiKey)
	if err != nil {
		return nil, err
	}

	if form.Action == "" {
		form = APIKeyFormData{
			Action:             fmt.Sprintf("/admin/api-keys/%d", apiKeyID),
			SubmitLabel:        "Save API key",
			Name:               apiKey.Name,
			Description:        apiKey.Description,
			ScopeMode:          apiKey.ScopeMode,
			ExpiresAt:          formatOptionalTimestamp(apiKey.ExpiresAt),
			CanDownload:        apiKey.CanDownload,
			CanUpload:          apiKey.CanUpload,
			CanDelete:          apiKey.CanDelete,
			CanManageProjects:  apiKey.CanManageProjects,
			SelectedProjectIDs: projectIDs(projectAccess),
			SelectedTagIDs:     tagIDs(tagAccess),
			Error:              form.Error,
			RevealKey:          form.RevealKey,
		}
	}

	data := &APIKeyPageData{
		PageData: h.basePageData(
			r,
			apiKey.Name,
			"Protected Admin",
			apiKey.Name,
			"Rotate permissions, change scope mode, and preview which active projects this key can currently reach.",
		),
		APIKey:             apiKey,
		Form:               form,
		AccessibleProjects: accessibleProjects,
		ToggleAction:       fmt.Sprintf("/admin/api-keys/%d/activate", apiKeyID),
	}

	if apiKey.IsActive {
		data.ToggleState = "revoke"
		data.ToggleLabel = "Revoke key"
	} else {
		data.ToggleState = "activate"
		data.ToggleLabel = "Activate key"
	}

	if err := h.populateAPIKeyChoices(r, data); err != nil {
		return nil, err
	}

	return data, nil
}

func (h *handler) populateAPIKeyChoices(r *http.Request, data *APIKeyPageData) error {
	projectItems, err := h.store.Projects.List(r.Context())
	if err != nil {
		return err
	}

	tagItems, err := h.store.Tags.List(r.Context())
	if err != nil {
		return err
	}

	selectedProjects := make(map[int64]struct{}, len(data.Form.SelectedProjectIDs))
	for _, id := range data.Form.SelectedProjectIDs {
		selectedProjects[id] = struct{}{}
	}

	selectedTags := make(map[int64]struct{}, len(data.Form.SelectedTagIDs))
	for _, id := range data.Form.SelectedTagIDs {
		selectedTags[id] = struct{}{}
	}

	data.ProjectChoices = make([]APIKeyProjectChoice, 0, len(projectItems))
	for _, item := range projectItems {
		_, selected := selectedProjects[item.Project.ID]
		data.ProjectChoices = append(data.ProjectChoices, APIKeyProjectChoice{
			Project:  item.Project,
			Selected: selected,
		})
	}

	data.TagChoices = make([]APIKeyTagChoice, 0, len(tagItems))
	for _, item := range tagItems {
		_, selected := selectedTags[item.Tag.ID]
		data.TagChoices = append(data.TagChoices, APIKeyTagChoice{
			Tag:      item.Tag,
			Selected: selected,
		})
	}

	return nil
}

func apiKeyFormDataFromRequest(r *http.Request, action, submitLabel string) (APIKeyFormData, string) {
	form := APIKeyFormData{
		Action:            action,
		SubmitLabel:       submitLabel,
		Name:              strings.TrimSpace(r.FormValue("name")),
		Description:       strings.TrimSpace(r.FormValue("description")),
		ScopeMode:         db.ScopeMode(strings.TrimSpace(r.FormValue("scope_mode"))),
		ExpiresAt:         strings.TrimSpace(r.FormValue("expires_at")),
		CanDownload:       r.FormValue("can_download") != "",
		CanUpload:         r.FormValue("can_upload") != "",
		CanDelete:         r.FormValue("can_delete") != "",
		CanManageProjects: r.FormValue("can_manage_projects") != "",
	}

	projectIDs, err := parseMultiIDList(r.Form["project_id"])
	if err != nil {
		return form, "Choose only valid projects."
	}
	form.SelectedProjectIDs = projectIDs

	tagIDs, err := parseMultiIDList(r.Form["tag_id"])
	if err != nil {
		return form, "Choose only valid tags."
	}
	form.SelectedTagIDs = tagIDs

	if form.Name == "" {
		return form, "API key name is required."
	}

	return form, ""
}

func parseMultiIDList(values []string) ([]int64, error) {
	result := make([]int64, 0, len(values))
	for _, raw := range values {
		raw = strings.TrimSpace(raw)
		if raw == "" {
			continue
		}

		value, err := strconv.ParseInt(raw, 10, 64)
		if err != nil || value <= 0 {
			return nil, fmt.Errorf("invalid id")
		}

		result = append(result, value)
	}

	return result, nil
}

func parseAPIKeyExpiration(raw string) (*time.Time, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, nil
	}

	if parsed, err := time.Parse(time.RFC3339, raw); err == nil {
		utc := parsed.UTC()
		return &utc, nil
	}

	if parsed, err := time.Parse("2006-01-02", raw); err == nil {
		utc := parsed.UTC()
		return &utc, nil
	}

	return nil, fmt.Errorf("invalid expiration")
}

func formatOptionalTimestamp(value *time.Time) string {
	if value == nil {
		return ""
	}

	return value.UTC().Format(time.RFC3339)
}

func apiKeyUserError(err error) string {
	message := strings.TrimSpace(err.Error())
	lower := strings.ToLower(message)

	switch {
	case strings.Contains(lower, "api key name is required"):
		return "API key name is required."
	case strings.Contains(lower, "scope mode is required"):
		return "Choose one scope mode."
	case strings.Contains(lower, "select at least one permission"):
		return "Select at least one permission."
	case strings.Contains(lower, "foreign key"):
		return "One of the selected projects or tags no longer exists."
	default:
		return ""
	}
}

func projectIDs(projects []db.Project) []int64 {
	ids := make([]int64, 0, len(projects))
	for _, project := range projects {
		ids = append(ids, project.ID)
	}

	return ids
}

func tagIDs(tags []db.Tag) []int64 {
	ids := make([]int64, 0, len(tags))
	for _, tag := range tags {
		ids = append(ids, tag.ID)
	}

	return ids
}

func apiKeyListFlash(code string) *FlashMessage {
	switch code {
	default:
		return nil
	}
}

func apiKeyDetailFlash(code string) *FlashMessage {
	switch code {
	case "api-key-updated":
		return &FlashMessage{Kind: "success", Message: "API key updated."}
	case "api-key-activated":
		return &FlashMessage{Kind: "success", Message: "API key activated."}
	case "api-key-revoked":
		return &FlashMessage{Kind: "success", Message: "API key revoked."}
	default:
		return nil
	}
}
