package httpserver

import (
	"database/sql"
	"encoding/json"
	"errors"
	"net/http"
	"strings"
	"time"

	"github.com/tim/cairnquire/apps/server/internal/auth"
	"github.com/tim/cairnquire/apps/server/internal/docs"
)

type permissionPageData struct {
	ResourceType string
	ResourceID   string
	Title        string
	Users        []permissionUserData
}

type permissionUserData struct {
	ID             string
	Email          string
	DisplayName    string
	Role           string
	CreatedAt      string
	LastSeenAt     string
	Permission     string
	PermissionText string
	GrantCreatedAt string
	GrantedBy      string
	GrantSummary   string
}

func (s *Server) handlePermissionsPage(w http.ResponseWriter, r *http.Request) {
	principal, _ := requirePrincipal(r)
	resourceType, resourceID := permissionResourceFromRequest(r)
	if resourceType == "" {
		resourceType = auth.ResourceDocument
	}
	if resourceType == auth.ResourceDocument {
		resourceID = strings.TrimSpace(resourceID)
		if resourceID == "" {
			resourceID = strings.TrimSpace(r.URL.Query().Get("path"))
		}
	}
	if resourceID == "" && resourceType != auth.ResourceGlobal && resourceType != auth.ResourceFolder {
		s.renderError(w, r, http.StatusBadRequest, "resource is required")
		return
	}
	if !s.canAdminResource(r, resourceType, resourceID, nil) {
		s.renderError(w, r, http.StatusNotFound, "document not found")
		return
	}
	data, err := s.buildPermissionPageData(r, principal, resourceType, resourceID)
	if err != nil {
		s.renderError(w, r, http.StatusInternalServerError, err.Error())
		return
	}
	s.renderTemplate(w, r, http.StatusOK, "permissions.gohtml", layoutData{
		Title:        "Permissions: " + data.Title,
		BodyClass:    "page-permissions",
		BodyTemplate: "permissions_content",
		Data:         data,
	})
}

func (s *Server) handlePermissionsForm(w http.ResponseWriter, r *http.Request) {
	principal, _ := requirePrincipal(r)
	if err := r.ParseForm(); err != nil {
		s.renderError(w, r, http.StatusBadRequest, "invalid permissions form")
		return
	}
	resourceType := auth.ResourceType(r.FormValue("resourceType"))
	resourceID := r.FormValue("resourceId")
	updates := permissionUpdatesFromForm(r)
	if _, err := s.auth.UpdateResourcePermissions(r.Context(), principal, resourceType, resourceID, updates); err != nil {
		s.renderError(w, r, http.StatusBadRequest, err.Error())
		return
	}
	http.Redirect(w, r, "/permissions?resourceType="+queryEscape(string(resourceType))+"&resourceId="+queryEscape(resourceID), http.StatusSeeOther)
}

func (s *Server) handlePermissionsAPI(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		s.handlePermissionsGetAPI(w, r)
	case http.MethodPatch:
		s.handlePermissionsPatchAPI(w, r)
	default:
		writeJSONWithStatus(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
	}
}

func (s *Server) handlePermissionsGetAPI(w http.ResponseWriter, r *http.Request) {
	principal, _ := requirePrincipal(r)
	resourceType, resourceID := permissionResourceFromRequest(r)
	if !s.canAdminResource(r, resourceType, resourceID, nil) {
		writeJSONWithStatus(w, http.StatusNotFound, map[string]string{"error": "resource not found"})
		return
	}
	permissions, err := s.auth.ListResourcePermissions(r.Context(), principal, resourceType, resourceID)
	if err != nil {
		writeJSONWithStatus(w, http.StatusForbidden, map[string]string{"error": err.Error()})
		return
	}
	users, err := s.auth.ListUsers(r.Context())
	if err != nil {
		writeJSONWithStatus(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"resourceType": resourceType,
		"resourceId":   resourceID,
		"permissions":  permissions,
		"users":        users,
	})
}

func (s *Server) handlePermissionsPatchAPI(w http.ResponseWriter, r *http.Request) {
	principal, _ := requirePrincipal(r)
	var req struct {
		ResourceType auth.ResourceType               `json:"resourceType"`
		ResourceID   string                          `json:"resourceId"`
		Users        []auth.ResourcePermissionUpdate `json:"users"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
		return
	}
	if !s.canAdminResource(r, req.ResourceType, req.ResourceID, nil) {
		writeJSONWithStatus(w, http.StatusNotFound, map[string]string{"error": "resource not found"})
		return
	}
	permissions, err := s.auth.UpdateResourcePermissions(r.Context(), principal, req.ResourceType, req.ResourceID, req.Users)
	if err != nil {
		writeJSONWithStatus(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"permissions": permissions})
}

func (s *Server) buildPermissionPageData(r *http.Request, principal auth.Principal, resourceType auth.ResourceType, resourceID string) (permissionPageData, error) {
	users, err := s.auth.ListUsers(r.Context())
	if err != nil {
		return permissionPageData{}, err
	}
	grants, err := s.auth.ListResourcePermissions(r.Context(), principal, resourceType, resourceID)
	if err != nil {
		return permissionPageData{}, err
	}
	grantsByUser := make(map[string]auth.Permission, len(grants))
	grantMetaByUser := make(map[string]auth.ResourcePermission, len(grants))
	for _, grant := range grants {
		grantsByUser[grant.UserID] = grant.Permission
		grantMetaByUser[grant.UserID] = grant
	}
	usersByID := make(map[string]auth.User, len(users))
	for _, user := range users {
		usersByID[user.ID] = user
	}
	result := permissionPageData{
		ResourceType: string(resourceType),
		ResourceID:   resourceID,
		Title:        permissionResourceTitle(resourceType, resourceID),
		Users:        make([]permissionUserData, 0, len(users)),
	}
	for _, user := range users {
		if user.Disabled {
			continue
		}
		permission := grantsByUser[user.ID]
		grant := grantMetaByUser[user.ID]
		result.Users = append(result.Users, permissionUserData{
			ID:             user.ID,
			Email:          user.Email,
			DisplayName:    user.DisplayName,
			Role:           string(user.Role),
			CreatedAt:      formatPermissionTime(user.CreatedAt),
			LastSeenAt:     formatPermissionTime(user.LastSeenAt),
			Permission:     string(permission),
			PermissionText: permissionLabel(permission),
			GrantCreatedAt: formatPermissionTime(grant.CreatedAt),
			GrantedBy:      grantActorLabel(grant.GrantedBy, usersByID),
			GrantSummary:   grantSummary(grant, usersByID),
		})
	}
	return result, nil
}

func permissionResourceTitle(resourceType auth.ResourceType, resourceID string) string {
	switch resourceType {
	case auth.ResourceDocument:
		return "Page " + resourceID
	case auth.ResourceFolder:
		if resourceID == "" {
			return "Root folder"
		}
		return "Folder " + resourceID
	case auth.ResourceCollection:
		return "Collection " + resourceID
	default:
		return string(resourceType)
	}
}

func permissionResourceFromRequest(r *http.Request) (auth.ResourceType, string) {
	return auth.ResourceType(r.URL.Query().Get("resourceType")), r.URL.Query().Get("resourceId")
}

func permissionUpdatesFromForm(r *http.Request) []auth.ResourcePermissionUpdate {
	var updates []auth.ResourcePermissionUpdate
	for key, values := range r.PostForm {
		if !strings.HasPrefix(key, "permission:") || len(values) == 0 {
			continue
		}
		userID := strings.TrimPrefix(key, "permission:")
		permission := auth.Permission(values[0])
		updates = append(updates, auth.ResourcePermissionUpdate{UserID: userID, Permission: permission})
	}
	return updates
}

func permissionLabel(permission auth.Permission) string {
	switch permission {
	case auth.PermissionRead:
		return "View"
	case auth.PermissionWrite:
		return "Edit"
	case auth.PermissionAdmin:
		return "Admin"
	default:
		return "No access"
	}
}

func grantSummary(grant auth.ResourcePermission, usersByID map[string]auth.User) string {
	if grant.ID == "" {
		return "No explicit grant"
	}
	parts := []string{"Explicit " + permissionLabel(grant.Permission)}
	if grant.CreatedAt.IsZero() {
		return strings.Join(parts, " · ")
	}
	parts = append(parts, "granted "+formatPermissionTime(grant.CreatedAt))
	if actor := grantActorLabel(grant.GrantedBy, usersByID); actor != "" {
		parts = append(parts, "by "+actor)
	}
	return strings.Join(parts, " · ")
}

func grantActorLabel(userID string, usersByID map[string]auth.User) string {
	if userID == "" {
		return ""
	}
	if user, ok := usersByID[userID]; ok {
		if user.DisplayName != "" {
			return user.DisplayName
		}
		return user.Email
	}
	return userID
}

func formatPermissionTime(value time.Time) string {
	if value.IsZero() {
		return "Never"
	}
	return value.Local().Format("Jan 2, 2006 3:04 PM")
}

func (s *Server) filterReadableDocuments(r *http.Request, records []docs.DocumentRecord) ([]docs.DocumentRecord, error) {
	filtered := make([]docs.DocumentRecord, 0, len(records))
	for _, record := range records {
		if ok, err := s.canReadDocumentRecord(r, record); err != nil {
			return nil, err
		} else if ok {
			filtered = append(filtered, record)
		}
	}
	return filtered, nil
}

func (s *Server) filterReadableSearchResults(r *http.Request, results []docs.SearchResult) ([]docs.SearchResult, error) {
	filtered := make([]docs.SearchResult, 0, len(results))
	for _, result := range results {
		record, err := s.repository.GetDocumentByPath(r.Context(), result.Path)
		if err != nil {
			if errors.Is(err, sql.ErrNoRows) {
				continue
			}
			return nil, err
		}
		if ok, err := s.canReadDocumentRecord(r, *record); err != nil {
			return nil, err
		} else if ok {
			filtered = append(filtered, result)
		}
	}
	return filtered, nil
}

func (s *Server) documentRecordForRequestPath(r *http.Request, requestPath string) (*docs.DocumentRecord, error) {
	clean := strings.Trim(strings.TrimSpace(requestPath), "/")
	if clean == "" {
		return nil, sql.ErrNoRows
	}
	if !strings.HasSuffix(strings.ToLower(clean), ".md") {
		clean += ".md"
	}
	return s.repository.GetDocumentByPath(r.Context(), clean)
}

func (s *Server) canReadDocumentRecord(r *http.Request, record docs.DocumentRecord) (bool, error) {
	return s.documentPermissionAllows(r, record.Path, record.Tags, auth.PermissionRead)
}

func (s *Server) canWriteDocumentRecord(r *http.Request, record docs.DocumentRecord) (bool, error) {
	return s.documentPermissionAllows(r, record.Path, record.Tags, auth.PermissionWrite)
}

func (s *Server) canAdminDocumentRecord(r *http.Request, record docs.DocumentRecord) (bool, error) {
	return s.documentPermissionAllows(r, record.Path, record.Tags, auth.PermissionAdmin)
}

func (s *Server) documentPermissionAllows(r *http.Request, path string, tags []string, required auth.Permission) (bool, error) {
	principal, ok := principalFromContext(r.Context())
	if !ok {
		return false, nil
	}
	if principal.Role == auth.RoleAdmin {
		return true, nil
	}
	permission, err := s.auth.EffectiveResourcePermission(r.Context(), principal, auth.ResourceDocument, path, tags)
	if err != nil {
		return false, err
	}
	return auth.PermissionAllows(permission, required), nil
}

func (s *Server) canAdminResource(r *http.Request, resourceType auth.ResourceType, resourceID string, tags []string) bool {
	principal, ok := principalFromContext(r.Context())
	if !ok {
		return false
	}
	if principal.Role == auth.RoleAdmin {
		return true
	}
	permission, err := s.auth.EffectiveResourcePermission(r.Context(), principal, resourceType, resourceID, tags)
	return err == nil && auth.PermissionAllows(permission, auth.PermissionAdmin)
}

func (s *Server) canWriteNewDocument(r *http.Request, pagePath string) bool {
	principal, ok := principalFromContext(r.Context())
	if !ok {
		return false
	}
	if principal.Role == auth.RoleAdmin {
		return true
	}
	return s.canWriteFolder(r, documentFolder(pagePath))
}

func (s *Server) canReadNewDocument(r *http.Request, pagePath string) bool {
	principal, ok := principalFromContext(r.Context())
	if !ok {
		return false
	}
	if principal.Role == auth.RoleAdmin {
		return true
	}
	return s.canReadFolder(r, documentFolder(pagePath))
}

func (s *Server) canReadFolder(r *http.Request, folder string) bool {
	principal, ok := principalFromContext(r.Context())
	if !ok {
		return false
	}
	if principal.Role == auth.RoleAdmin {
		return true
	}
	permission, err := s.auth.EffectiveResourcePermission(r.Context(), principal, auth.ResourceFolder, folder, nil)
	return err == nil && auth.PermissionAllows(permission, auth.PermissionRead)
}

func (s *Server) canWriteFolder(r *http.Request, folder string) bool {
	principal, ok := principalFromContext(r.Context())
	if !ok {
		return false
	}
	if principal.Role == auth.RoleAdmin {
		return true
	}
	permission, err := s.auth.EffectiveResourcePermission(r.Context(), principal, auth.ResourceFolder, folder, nil)
	return err == nil && auth.PermissionAllows(permission, auth.PermissionWrite)
}

func (s *Server) grantDocumentCreatorAdmin(r *http.Request, path string) error {
	principal, ok := principalFromContext(r.Context())
	if !ok || principal.UserID == "" {
		return nil
	}
	return s.auth.EnsureResourcePermission(r.Context(), principal, auth.ResourceDocument, path, auth.ResourcePermissionUpdate{
		UserID:     principal.UserID,
		Permission: auth.PermissionAdmin,
	})
}

func lastPathSegment(path string) string {
	trimmed := strings.Trim(path, "/")
	if trimmed == "" {
		return ""
	}
	parts := strings.Split(trimmed, "/")
	return parts[len(parts)-1]
}

func documentFolder(path string) string {
	trimmed := strings.Trim(path, "/")
	segment := lastPathSegment(trimmed)
	if segment == "" || segment == trimmed {
		return ""
	}
	return strings.TrimSuffix(trimmed, "/"+segment)
}

func queryEscape(value string) string {
	return strings.NewReplacer("%", "%25", " ", "%20", "?", "%3F", "&", "%26", "=", "%3D", "#", "%23").Replace(value)
}
