package httpapi

import (
	"context"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"productier/apps/backend/internal/authsession"
	"productier/apps/backend/internal/store"
)

const sessionContextKey = "sessionUser"

func (s *Server) registerRoutes() {
	v1 := s.engine.Group("/v1")
	{
		v1.GET("/health", func(c *gin.Context) {
			now := time.Now().UTC()
			probeCtx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
			defer cancel()

			storageStatus := gin.H{
				"provider": s.files.Provider(),
				"ok":       true,
			}
			if err := s.files.Probe(probeCtx); err != nil {
				storageStatus["ok"] = false
				storageStatus["error"] = err.Error()
				c.JSON(http.StatusServiceUnavailable, gin.H{
					"ok":        false,
					"mode":      s.mode,
					"timestamp": now,
					"storage":   storageStatus,
				})
				return
			}

			c.JSON(http.StatusOK, gin.H{
				"ok":        true,
				"mode":      s.mode,
				"timestamp": now,
				"storage":   storageStatus,
			})
		})

		v1.GET("/metrics", func(c *gin.Context) {
			if !s.authorizeMetricsRequest(c) {
				return
			}
			c.JSON(http.StatusOK, s.metrics.snapshot())
		})

		v1.GET("/metrics/prometheus", func(c *gin.Context) {
			if !s.authorizeMetricsRequest(c) {
				return
			}
			c.Data(http.StatusOK, "text/plain; version=0.0.4; charset=utf-8", []byte(s.metrics.snapshotPrometheus()))
		})

		v1.GET("/invites/:token", func(c *gin.Context) {
			invite, err := s.store.GetInviteByToken(c.Param("token"))
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": invite})
		})
	}

	authorized := v1.Group("/")
	authorized.Use(s.requireSession())
	{
		authorized.GET("/workspaces", func(c *gin.Context) {
			user := s.sessionUser(c)
			workspaces := s.store.ListWorkspaces()
			visible := make([]store.Workspace, 0, len(workspaces))
			for _, workspace := range workspaces {
				if _, ok := s.requireWorkspaceMemberByEmail(workspace.Slug, user.Email); ok {
					visible = append(visible, workspace)
				}
			}
			c.JSON(http.StatusOK, gin.H{"data": visible})
		})

		authorized.GET("/members", func(c *gin.Context) {
			workspaceSlug := c.Query("workspaceSlug")
			if _, ok := s.requireWorkspaceMember(c, workspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": s.store.ListMembers(workspaceSlug)})
		})

		authorized.PATCH("/members/:memberId", func(c *gin.Context) {
			member, err := s.store.GetMemberByID(c.Param("memberId"))
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			actingMember, ok := s.requireWorkspaceMember(c, member.WorkspaceSlug)
			if !ok {
				return
			}
			if actingMember.Role != "owner" && actingMember.Role != "admin" {
				s.writeStatusError(c, http.StatusForbidden, "member management permissions required")
				return
			}

			var input store.UpdateMemberInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}

			updated, err := s.store.UpdateMember(member.ID, input)
			if err != nil {
				switch err.Error() {
				case "invalid member role", "invalid member status":
					s.writeStatusError(c, http.StatusBadRequest, err.Error())
				case "workspace must have at least one active owner":
					s.writeStatusError(c, http.StatusConflict, err.Error())
				default:
					s.writeStatusError(c, http.StatusNotFound, err.Error())
				}
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": updated})
		})

		authorized.GET("/invites", func(c *gin.Context) {
			workspaceSlug := c.Query("workspaceSlug")
			if _, ok := s.requireWorkspaceMember(c, workspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": s.store.ListInvites(workspaceSlug)})
		})

		authorized.POST("/invites", func(c *gin.Context) {
			var input store.CreateInviteInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}

			member, ok := s.requireWorkspaceMember(c, input.WorkspaceSlug)
			if !ok {
				return
			}
			if member.Role != "owner" && member.Role != "admin" {
				s.writeStatusError(c, http.StatusForbidden, "invite permissions required")
				return
			}

			c.JSON(http.StatusCreated, gin.H{"data": s.store.CreateInvite(input)})
		})

		authorized.POST("/invites/:token/revoke", func(c *gin.Context) {
			invite, err := s.store.GetInviteByID(c.Param("token"))
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			member, ok := s.requireWorkspaceMember(c, invite.WorkspaceSlug)
			if !ok {
				return
			}
			if member.Role != "owner" && member.Role != "admin" {
				s.writeStatusError(c, http.StatusForbidden, "invite permissions required")
				return
			}
			if err := s.store.RevokeInvite(invite.ID); err != nil {
				switch err.Error() {
				case "only pending invites can be revoked":
					s.writeStatusError(c, http.StatusConflict, err.Error())
				default:
					s.writeStatusError(c, http.StatusNotFound, err.Error())
				}
				return
			}
			c.Status(http.StatusNoContent)
		})

		authorized.POST("/invites/:token/accept", func(c *gin.Context) {
			var input store.AcceptInviteInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}

			user := s.sessionUser(c)
			invite, err := s.store.AcceptInvite(c.Param("token"), store.AcceptInviteInput{
				Name:  user.Name,
				Email: user.Email,
			})
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": invite})
		})

		authorized.GET("/activity", func(c *gin.Context) {
			workspaceSlug := c.Query("workspaceSlug")
			if strings.TrimSpace(workspaceSlug) == "" {
				s.writeStatusError(c, http.StatusBadRequest, "workspaceSlug is required")
				return
			}
			if _, ok := s.requireWorkspaceMember(c, workspaceSlug); !ok {
				return
			}
			params, err := parseActivityListParams(c.Query("limit"), c.Query("type"), c.Query("q"))
			if err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}

			activities := s.store.ListActivities(workspaceSlug)
			c.JSON(http.StatusOK, gin.H{"data": filterActivityEntries(activities, params)})
		})

		authorized.GET("/board-groups", func(c *gin.Context) {
			workspaceSlug := c.Query("workspaceSlug")
			if _, ok := s.requireWorkspaceMember(c, workspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": s.store.ListBoardGroups(workspaceSlug)})
		})

		authorized.POST("/board-groups", func(c *gin.Context) {
			var input store.CreateBoardGroupInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}
			if _, ok := s.requireWorkspaceMember(c, input.WorkspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusCreated, gin.H{"data": s.store.CreateBoardGroup(input)})
		})

		authorized.PATCH("/board-groups/:groupId", func(c *gin.Context) {
			group, err := s.store.GetBoardGroupByID(c.Param("groupId"))
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			if _, ok := s.requireWorkspaceMember(c, group.WorkspaceSlug); !ok {
				return
			}

			var input store.UpdateBoardGroupInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}

			updated, err := s.store.UpdateBoardGroup(c.Param("groupId"), input)
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": updated})
		})

		authorized.GET("/labels", func(c *gin.Context) {
			workspaceSlug := c.Query("workspaceSlug")
			if _, ok := s.requireWorkspaceMember(c, workspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": s.store.ListLabels(workspaceSlug)})
		})

		authorized.POST("/labels", func(c *gin.Context) {
			var input store.CreateLabelInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}
			if _, ok := s.requireWorkspaceMember(c, input.WorkspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusCreated, gin.H{"data": s.store.CreateLabel(input)})
		})

		authorized.GET("/tasks", func(c *gin.Context) {
			workspaceSlug := c.Query("workspaceSlug")
			if _, ok := s.requireWorkspaceMember(c, workspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": s.store.ListTasks(workspaceSlug)})
		})

		authorized.POST("/tasks", func(c *gin.Context) {
			var input store.CreateTaskInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}
			if _, ok := s.requireWorkspaceMember(c, input.WorkspaceSlug); !ok {
				return
			}
			task := s.store.CreateTask(input)

			// Trigger webhooks for task creation
			s.store.TriggerWebhooks(input.WorkspaceSlug, "task.created", map[string]interface{}{
				"taskId": task.ID,
				"title":  task.Title,
			})

			c.JSON(http.StatusCreated, gin.H{"data": task})
		})

		authorized.PATCH("/tasks/:taskId", func(c *gin.Context) {
			task, err := s.store.GetTaskByID(c.Param("taskId"))
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			if _, ok := s.requireWorkspaceMember(c, task.WorkspaceSlug); !ok {
				return
			}

			var input store.UpdateTaskInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}

			// Check if assignee is being set (task assignment notification)
			if input.AssigneeID != nil && *input.AssigneeID != "" {
				// Get assignee email from member ID
				members := s.store.ListMembers(task.WorkspaceSlug)
				for _, member := range members {
					if member.ID == *input.AssigneeID && member.Status == "active" {
						// Create notification for the assignee
						s.store.CreateNotificationForTaskAssignment(
							task.WorkspaceSlug,
							member.Email,
							task.Title,
							task.ID,
						)
						break
					}
				}
			}

			// Check if status is being changed to done (task completion notification)
			if input.Status != nil && *input.Status == "done" && task.Status != "done" && task.AssigneeID != nil {
				// Notify the task creator or workspace owner
				members := s.store.ListMembers(task.WorkspaceSlug)
				for _, member := range members {
					if member.Role == "owner" || member.Role == "admin" {
						s.store.CreateNotificationForTaskCompletion(
							task.WorkspaceSlug,
							member.Email,
							task.Title,
							task.ID,
						)
						break
					}
				}
			}

			updated, err := s.store.UpdateTask(c.Param("taskId"), input)
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}

			// Trigger webhooks for task updates
			s.store.TriggerWebhooks(task.WorkspaceSlug, "task.updated", map[string]interface{}{
				"taskId": task.ID,
				"title":  task.Title,
			})

			c.JSON(http.StatusOK, gin.H{"data": updated})
		})

		authorized.GET("/calendar/events", func(c *gin.Context) {
			workspaceSlug := c.Query("workspaceSlug")
			if _, ok := s.requireWorkspaceMember(c, workspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": s.store.ListEvents(workspaceSlug)})
		})

		authorized.POST("/calendar/events", func(c *gin.Context) {
			var input store.CreateEventInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}
			if _, ok := s.requireWorkspaceMember(c, input.WorkspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusCreated, gin.H{"data": s.store.CreateEvent(input)})
		})

		authorized.PATCH("/calendar/events/:eventId", func(c *gin.Context) {
			event, err := s.store.GetEventByID(c.Param("eventId"))
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			if _, ok := s.requireWorkspaceMember(c, event.WorkspaceSlug); !ok {
				return
			}

			var input store.UpdateEventInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}

			updated, err := s.store.UpdateEvent(c.Param("eventId"), input)
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": updated})
		})

		authorized.GET("/notes", func(c *gin.Context) {
			workspaceSlug := c.Query("workspaceSlug")
			if _, ok := s.requireWorkspaceMember(c, workspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": s.store.ListNotes(workspaceSlug)})
		})

		authorized.POST("/notes", func(c *gin.Context) {
			var input store.CreateNoteInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}
			if _, ok := s.requireWorkspaceMember(c, input.WorkspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusCreated, gin.H{"data": s.store.CreateNote(input)})
		})

		authorized.PATCH("/notes/:noteId", func(c *gin.Context) {
			note, err := s.store.GetNoteByID(c.Param("noteId"))
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			if _, ok := s.requireWorkspaceMember(c, note.WorkspaceSlug); !ok {
				return
			}

			var input store.UpdateNoteInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}

			updated, err := s.store.UpdateNote(c.Param("noteId"), input)
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": updated})
		})

		authorized.GET("/focus/sessions", func(c *gin.Context) {
			workspaceSlug := c.Query("workspaceSlug")
			if _, ok := s.requireWorkspaceMember(c, workspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": s.store.ListFocusSessions(workspaceSlug)})
		})

		authorized.POST("/focus/sessions", func(c *gin.Context) {
			var input store.CreateFocusSessionInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}
			if _, ok := s.requireWorkspaceMember(c, input.WorkspaceSlug); !ok {
				return
			}
			c.JSON(http.StatusCreated, gin.H{"data": s.store.CreateFocusSession(input)})
		})

		authorized.PATCH("/focus/sessions/:sessionId", func(c *gin.Context) {
			session, err := s.store.GetFocusSessionByID(c.Param("sessionId"))
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			if _, ok := s.requireWorkspaceMember(c, session.WorkspaceSlug); !ok {
				return
			}

			var input store.UpdateFocusSessionInput
			if err := c.ShouldBindJSON(&input); err != nil {
				s.writeStatusError(c, http.StatusBadRequest, err.Error())
				return
			}

			updated, err := s.store.UpdateFocusSession(c.Param("sessionId"), input)
			if err != nil {
				s.writeStatusError(c, http.StatusNotFound, err.Error())
				return
			}
			c.JSON(http.StatusOK, gin.H{"data": updated})
		})

		s.registerTaskAttachmentRoutes(authorized)
		s.registerMailRoutes(authorized)
		s.registerCRMRoutes(authorized)
		s.registerProductivityRoutes(authorized)
		s.registerIntegrationRoutes(authorized)
		s.registerOAuthRoutes(authorized)
	}
}

func (s *Server) requireSession() gin.HandlerFunc {
	return func(c *gin.Context) {
		user, err := s.authClient.GetUser(c.Request.Context(), c.GetHeader("Cookie"))
		if err != nil {
			s.writeStatusError(c, http.StatusUnauthorized, "session lookup failed")
			c.Abort()
			return
		}
		if user == nil {
			s.writeStatusError(c, http.StatusUnauthorized, "authentication required")
			c.Abort()
			return
		}
		c.Set(sessionContextKey, user)
		c.Next()
	}
}

func (s *Server) sessionUser(c *gin.Context) *authsession.User {
	value, exists := c.Get(sessionContextKey)
	if !exists {
		return nil
	}
	user, _ := value.(*authsession.User)
	return user
}

func (s *Server) requireWorkspaceMember(c *gin.Context, workspaceSlug string) (store.Member, bool) {
	user := s.sessionUser(c)
	if user == nil {
		s.writeStatusError(c, http.StatusUnauthorized, "authentication required")
		return store.Member{}, false
	}
	member, ok := s.requireWorkspaceMemberByEmail(workspaceSlug, user.Email)
	if !ok {
		s.writeStatusError(c, http.StatusForbidden, "workspace membership required")
		return store.Member{}, false
	}
	return member, true
}

func (s *Server) requireWorkspaceMemberByEmail(workspaceSlug string, email string) (store.Member, bool) {
	for _, member := range s.store.ListMembers(workspaceSlug) {
		if strings.EqualFold(member.Email, email) && member.Status == "active" {
			return member, true
		}
	}
	return store.Member{}, false
}
