package handlers

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

	"goyco/internal/database"
	"goyco/internal/dto"
	"goyco/internal/repositories"
	"goyco/internal/security"
	"goyco/internal/services"

	"github.com/go-chi/chi/v5"
	"github.com/jackc/pgconn"
)

type PostHandler struct {
	postRepo     repositories.PostRepository
	titleFetcher services.TitleFetcher
	voteService  *services.VoteService
	postQueries  *services.PostQueries
}

func NewPostHandler(postRepo repositories.PostRepository, titleFetcher services.TitleFetcher, voteService *services.VoteService) *PostHandler {
	return &PostHandler{
		postRepo:     postRepo,
		titleFetcher: titleFetcher,
		voteService:  voteService,
		postQueries:  services.NewPostQueries(postRepo, voteService),
	}
}

type PostResponse = CommonResponse

// @Summary Get posts
// @Description Get a list of posts with pagination. Posts include vote statistics (up_votes, down_votes, score) and current user's vote status.
// @Tags posts
// @Accept json
// @Produce json
// @Param limit query int false "Number of posts to return" default(20)
// @Param offset query int false "Number of posts to skip" default(0)
// @Success 200 {object} PostResponse "Posts retrieved successfully with vote statistics"
// @Failure 400 {object} PostResponse "Invalid pagination parameters"
// @Failure 500 {object} PostResponse "Internal server error"
// @Router /api/posts [get]
func (h *PostHandler) GetPosts(w http.ResponseWriter, r *http.Request) {
	limit, offset := parsePagination(r)

	opts := services.QueryOptions{
		Limit:  limit,
		Offset: offset,
	}

	ctx := NewVoteContext(r)

	posts, err := h.postQueries.GetAll(opts, ctx)
	if err != nil {
		SendErrorResponse(w, "Failed to fetch posts", http.StatusInternalServerError)
		return
	}

	responseDTO := dto.ToPostListDTO(posts, limit, offset)
	SendSuccessResponse(w, "Posts retrieved successfully", responseDTO)
}

// @Summary Get a single post
// @Description Get a post by ID with vote statistics and current user's vote status
// @Tags posts
// @Accept json
// @Produce json
// @Param id path int true "Post ID"
// @Success 200 {object} PostResponse "Post retrieved successfully with vote statistics"
// @Failure 400 {object} PostResponse "Invalid post ID"
// @Failure 404 {object} PostResponse "Post not found"
// @Failure 500 {object} PostResponse "Internal server error"
// @Router /api/posts/{id} [get]
func (h *PostHandler) GetPost(w http.ResponseWriter, r *http.Request) {
	postID, ok := ParseUintParam(w, r, "id", "Post")
	if !ok {
		return
	}

	ctx := NewVoteContext(r)

	post, err := h.postQueries.GetByID(postID, ctx)
	if !HandleRepoError(w, err, "Post") {
		return
	}

	postDTO := dto.ToPostDTO(post)
	SendSuccessResponse(w, "Post retrieved successfully", postDTO)
}

// @Summary Create a new post
// @Description Create a new post with URL and optional title
// @Tags posts
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body dto.CreatePostRequest true "Post data"
// @Success 201 {object} PostResponse
// @Failure 400 {object} PostResponse "Invalid request data or validation failed"
// @Failure 401 {object} PostResponse "Authentication required"
// @Failure 409 {object} PostResponse "URL already submitted"
// @Failure 502 {object} PostResponse "Failed to fetch title from URL"
// @Failure 500 {object} PostResponse "Internal server error"
// @Router /api/posts [post]
func (h *PostHandler) CreatePost(w http.ResponseWriter, r *http.Request) {
	request, ok := GetValidatedDTO[dto.CreatePostRequest](w, r)
	if !ok {
		return
	}

	userID, ok := RequireAuth(w, r)
	if !ok {
		return
	}

	title := security.SanitizeInput(request.Title)
	url := security.SanitizeURL(request.URL)
	content := security.SanitizePostContent(request.Content)

	if url == "" {
		SendErrorResponse(w, "Invalid URL", http.StatusBadRequest)
		return
	}

	if title == "" && h.titleFetcher != nil {
		titleCtx, cancel := context.WithTimeout(r.Context(), 7*time.Second)
		defer cancel()

		fetchedTitle, err := h.titleFetcher.FetchTitle(titleCtx, url)
		if err != nil {
			switch {
			case errors.Is(err, services.ErrUnsupportedScheme):
				SendErrorResponse(w, "Only HTTP and HTTPS URLs are supported", http.StatusBadRequest)
			case errors.Is(err, services.ErrTitleNotFound):
				SendErrorResponse(w, "Title could not be extracted from the provided URL", http.StatusBadRequest)
			default:
				SendErrorResponse(w, "Failed to fetch title from URL", http.StatusBadGateway)
			}
			return
		}

		title = fetchedTitle
	}

	if title == "" {
		SendErrorResponse(w, "Title is required", http.StatusBadRequest)
		return
	}

	if len(title) < 3 {
		SendErrorResponse(w, "Title must be at least 3 characters", http.StatusBadRequest)
		return
	}

	if len(title) > 200 {
		SendErrorResponse(w, "Title must be at most 200 characters", http.StatusBadRequest)
		return
	}

	if len(content) > 10000 {
		SendErrorResponse(w, "Content must be at most 10000 characters", http.StatusBadRequest)
		return
	}

	post := &database.Post{
		Title:    title,
		URL:      url,
		Content:  content,
		AuthorID: &userID,
	}

	if err := h.postRepo.Create(post); err != nil {
		if errMsg, status := translatePostCreateError(err); status != 0 {
			SendErrorResponse(w, errMsg, status)
			return
		}

		SendErrorResponse(w, "Failed to create post", http.StatusInternalServerError)
		return
	}

	postDTO := dto.ToPostDTO(post)
	SendCreatedResponse(w, "Post created successfully", postDTO)
}

// @Summary Search posts
// @Description Search posts by title or content keywords. Results include vote statistics and current user's vote status.
// @Tags posts
// @Accept json
// @Produce json
// @Param q query string false "Search term"
// @Param limit query int false "Number of posts to return" default(20)
// @Param offset query int false "Number of posts to skip" default(0)
// @Success 200 {object} PostResponse "Search results with vote statistics"
// @Failure 400 {object} PostResponse "Invalid search parameters"
// @Failure 500 {object} PostResponse "Internal server error"
// @Router /api/posts/search [get]
func (h *PostHandler) SearchPosts(w http.ResponseWriter, r *http.Request) {
	query := strings.TrimSpace(r.URL.Query().Get("q"))
	limit, offset := parsePagination(r)

	opts := services.QueryOptions{
		Limit:  limit,
		Offset: offset,
	}

	ctx := NewVoteContext(r)

	posts, err := h.postQueries.GetSearch(query, opts, ctx)
	if err != nil {
		if searchErr, ok := err.(*repositories.SearchError); ok {
			SendErrorResponse(w, "Invalid search query: "+searchErr.Message, http.StatusBadRequest)
			return
		}
		SendErrorResponse(w, "Failed to search posts", http.StatusInternalServerError)
		return
	}

	responseDTO := dto.ToSearchPostListDTO(posts, query, limit, offset)
	SendSuccessResponse(w, "Search results retrieved successfully", responseDTO)
}

// @Summary Update a post
// @Description Update the title and content of a post owned by the authenticated user
// @Tags posts
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Post ID"
// @Param request body dto.UpdatePostRequest true "Post update data"
// @Success 200 {object} PostResponse "Post updated successfully"
// @Failure 400 {object} PostResponse "Invalid request data or validation failed"
// @Failure 401 {object} PostResponse "Authentication required"
// @Failure 403 {object} PostResponse "Not authorized to update this post"
// @Failure 404 {object} PostResponse "Post not found"
// @Failure 500 {object} PostResponse "Internal server error"
// @Router /api/posts/{id} [put]
func (h *PostHandler) UpdatePost(w http.ResponseWriter, r *http.Request) {
	userID, ok := RequireAuth(w, r)
	if !ok {
		return
	}

	postID, ok := ParseUintParam(w, r, "id", "Post")
	if !ok {
		return
	}

	post, err := h.postRepo.GetByID(postID)
	if !HandleRepoError(w, err, "Post") {
		return
	}

	if post.AuthorID == nil || *post.AuthorID != userID {
		SendErrorResponse(w, "You can only edit your own posts", http.StatusForbidden)
		return
	}

	request, ok := GetValidatedDTO[dto.UpdatePostRequest](w, r)
	if !ok {
		return
	}

	title := security.SanitizeInput(request.Title)
	content := security.SanitizePostContent(request.Content)

	post.Title = title
	post.Content = content

	if err := h.postRepo.Update(post); err != nil {
		SendErrorResponse(w, "Failed to update post", http.StatusInternalServerError)
		return
	}

	postDTO := dto.ToPostDTO(post)
	SendSuccessResponse(w, "Post updated successfully", postDTO)
}

// @Summary Delete a post
// @Description Delete a post owned by the authenticated user
// @Tags posts
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "Post ID"
// @Success 200 {object} PostResponse "Post deleted successfully"
// @Failure 400 {object} PostResponse "Invalid post ID"
// @Failure 401 {object} PostResponse "Authentication required"
// @Failure 403 {object} PostResponse "Not authorized to delete this post"
// @Failure 404 {object} PostResponse "Post not found"
// @Failure 500 {object} PostResponse "Internal server error"
// @Router /api/posts/{id} [delete]
func (h *PostHandler) DeletePost(w http.ResponseWriter, r *http.Request) {
	userID, ok := RequireAuth(w, r)
	if !ok {
		return
	}

	postID, ok := ParseUintParam(w, r, "id", "Post")
	if !ok {
		return
	}

	post, err := h.postRepo.GetByID(postID)
	if !HandleRepoError(w, err, "Post") {
		return
	}

	if post.AuthorID == nil || *post.AuthorID != userID {
		SendErrorResponse(w, "You can only delete your own posts", http.StatusForbidden)
		return
	}

	if err := h.voteService.DeleteVotesByPostID(postID); err != nil {
		SendErrorResponse(w, "Failed to delete post votes", http.StatusInternalServerError)
		return
	}

	if err := h.postRepo.Delete(postID); err != nil {
		SendErrorResponse(w, "Failed to delete post", http.StatusInternalServerError)
		return
	}

	SendSuccessResponse(w, "Post deleted successfully", dto.EmptyResponseDTO{})
}

// @Summary Fetch title from URL
// @Description Fetch the HTML title for the provided URL
// @Tags posts
// @Accept json
// @Produce json
// @Param url query string true "URL to inspect"
// @Success 200 {object} PostResponse "Title fetched successfully"
// @Failure 400 {object} PostResponse "Invalid URL or URL parameter missing"
// @Failure 501 {object} PostResponse "Title fetching is not available"
// @Failure 502 {object} PostResponse "Failed to fetch title from URL"
// @Router /api/posts/title [get]
func (h *PostHandler) FetchTitleFromURL(w http.ResponseWriter, r *http.Request) {
	if h.titleFetcher == nil {
		SendErrorResponse(w, "Title fetching is not available", http.StatusNotImplemented)
		return
	}

	requestedURL := strings.TrimSpace(r.URL.Query().Get("url"))
	if requestedURL == "" {
		SendErrorResponse(w, "URL query parameter is required", http.StatusBadRequest)
		return
	}

	titleCtx, cancel := context.WithTimeout(r.Context(), 7*time.Second)
	defer cancel()

	title, err := h.titleFetcher.FetchTitle(titleCtx, requestedURL)
	if err != nil {
		switch {
		case errors.Is(err, services.ErrUnsupportedScheme):
			SendErrorResponse(w, "Only HTTP and HTTPS URLs are supported", http.StatusBadRequest)
		case errors.Is(err, services.ErrTitleNotFound):
			SendErrorResponse(w, "Title could not be extracted from the provided URL", http.StatusBadRequest)
		default:
			SendErrorResponse(w, "Failed to fetch title from URL", http.StatusBadGateway)
		}
		return
	}

	responseDTO := dto.TitleResponseDTO{
		Title: title,
	}
	SendSuccessResponse(w, "Title fetched successfully", responseDTO)
}

func translatePostCreateError(err error) (string, int) {
	var pgErr *pgconn.PgError
	if errors.As(err, &pgErr) {
		switch pgErr.Code {
		case "23505":
			return "This URL has already been submitted.", http.StatusConflict
		case "23503":
			return "Author account not found. Please sign in again.", http.StatusUnauthorized
		}
	}

	errStr := err.Error()
	if strings.Contains(errStr, "UNIQUE constraint") || strings.Contains(errStr, "duplicate") {
		return "This URL has already been submitted.", http.StatusConflict
	}

	return "", 0
}

func (h *PostHandler) MountRoutes(r chi.Router, config RouteModuleConfig) {
	public := r
	if config.GeneralRateLimit != nil {
		public = config.GeneralRateLimit(r)
	}
	public.Get("/posts", h.GetPosts)
	public.Get("/posts/search", h.SearchPosts)
	public.Get("/posts/title", h.FetchTitleFromURL)
	public.Get("/posts/{id}", h.GetPost)

	protected := r
	if config.AuthMiddleware != nil {
		protected = r.With(config.AuthMiddleware)
	}
	if config.GeneralRateLimit != nil {
		protected = config.GeneralRateLimit(protected)
	}
	protected.Post("/posts", WithValidation[dto.CreatePostRequest](config.ValidationMiddleware, h.CreatePost))
	protected.Put("/posts/{id}", WithValidation[dto.UpdatePostRequest](config.ValidationMiddleware, h.UpdatePost))
	protected.Delete("/posts/{id}", h.DeletePost)
}
