package journal

import (
	"errors"
	"fmt"
	"html"
	"html/template"
	"log"
	"net/http"
	"strconv"
	"time"

	"gitlab.mareshq.com/lab/journal/httpserver"
	"gorm.io/gorm"
)

const dateFormat = "2006-01-02"

type httpEntry struct {
	ID            int
	Title         string
	FormattedDate string
	Content       string
	HTMLContent   template.HTML
}

func httpEntryFromEntry(entry Entry) httpEntry {
	return httpEntry{
		ID:            entry.ID,
		Title:         entry.Title,
		FormattedDate: entry.Date.Format(dateFormat),
		Content:       html.UnescapeString(entry.Content),
		HTMLContent:   renderContent(entry.Content),
	}
}

type Server struct {
	repo             *Repository
	logger           *log.Logger
	templateRenderer *httpserver.TemplateRenderer
}

func NewServer(repo *Repository, templateRenderer *httpserver.TemplateRenderer, logger *log.Logger) *Server {
	return &Server{
		repo:             repo,
		logger:           logger,
		templateRenderer: templateRenderer,
	}
}

func (s *Server) HandleEntries(w http.ResponseWriter, r *http.Request) {
	type templateProperties struct {
		Entries    []httpEntry
		Pagination struct {
			Next int
			Prev int
		}
	}

	limit := 24

	var offsetID *int = nil
	strOffset := r.URL.Query().Get("offset")

	if strOffset != "" {
		parsedOffset, err := strconv.Atoi(strOffset)
		if err == nil && parsedOffset > 0 {
			offsetID = &parsedOffset
		}
	}

	entriesFromDB, err := s.repo.Find(r.Context(), limit, offsetID)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		httpserver.NotFound(w, r)
		s.logger.Println("No entries found:", err)

		return
	}

	if err != nil {
		httpserver.InternalServerError(w, r)
		s.logger.Println("Error finding entries:", err)

		return
	}

	prev := 0
	next := 0

	if offsetID != nil {
		if *offsetID > 0 {
			prev = *offsetID - limit
		}
		if len(entriesFromDB) == limit {
			next = *offsetID + limit
		}
	} else {
		if len(entriesFromDB) == limit {
			next = limit
		}
		if len(entriesFromDB) > 0 {
			prev = entriesFromDB[0].ID - limit
		}
	}

	if len(entriesFromDB) == limit {
		next = entriesFromDB[len(entriesFromDB)-1].ID
	}

	if offsetID != nil {
		prev = *offsetID - limit // no fancy DB pagination, just simple/naive cursor
	}

	// normalize offset IDs
	if prev < 0 {
		prev = 0
	}
	if next < 0 {
		next = 0
	}

	tplProps := templateProperties{
		Entries: make([]httpEntry, len(entriesFromDB)),
		Pagination: struct {
			Next int
			Prev int
		}{
			Next: next,
			Prev: prev,
		},
	}

	for i, entry := range entriesFromDB {
		tplProps.Entries[i] = httpEntryFromEntry(*entry)
	}

	s.templateRenderer.Render(w, "entries.html", tplProps)
}

func (s *Server) HandleEntryByID(w http.ResponseWriter, r *http.Request) {
	strID := r.PathValue("id")
	id, err := strconv.Atoi(strID)
	if err != nil {
		httpserver.BadRequest(w, r)
		log.Println("Error converting ID to int:", err)
		return
	}

	entryFromDB, err := s.repo.FindByID(r.Context(), id)
	if err != nil {
		httpserver.InternalServerError(w, r)
		log.Println("Error finding entry by ID:", err)

		return
	}

	entry := httpEntryFromEntry(*entryFromDB)

	s.templateRenderer.Render(w, "entry.html", entry)
}

func (s *Server) HandleDeleteEntryByID(w http.ResponseWriter, r *http.Request) {
	strID := r.PostFormValue("id")

	s.logger.Println("Deleting entry with ID:", strID)

	id, err := strconv.Atoi(strID)
	if err != nil {
		httpserver.BadRequest(w, r)
		s.logger.Printf("Error converting ID to int: %v, got: %v\n", err, strID)

		return
	}

	err = s.repo.Delete(r.Context(), id)
	if err != nil {
		httpserver.InternalServerError(w, r)
		s.logger.Println("Error deleting entry by ID:", err)

		return
	}

	http.Redirect(w, r, "/entry/deleted", http.StatusSeeOther)
}

func (s *Server) HandleNewEntry(w http.ResponseWriter, r *http.Request) {
	templateProps := struct {
		Today string
	}{
		Today: time.Now().Format(dateFormat),
	}

	s.templateRenderer.Render(w, "new-entry.html", templateProps)
}

func (s *Server) HandleSubmitNewEntry(w http.ResponseWriter, r *http.Request) {
	title := r.PostFormValue("title")
	date := r.PostFormValue("date")
	content := r.PostFormValue("content")

	if title == "" || date == "" || content == "" {
		httpserver.BadRequest(w, r)
		s.logger.Println("Missing required fields")

		return
	}

	parsedDate, err := time.Parse(dateFormat, date)
	if err != nil {
		httpserver.BadRequest(w, r)
		s.logger.Println("Error parsing date:", err)

		return
	}

	e := &Entry{
		Title:   title,
		Date:    parsedDate,
		Content: content,
	}

	err = s.repo.Create(r.Context(), e)
	if err != nil {
		httpserver.InternalServerError(w, r)
		s.logger.Println("Error creating entry:", err)

		return
	}

	http.Redirect(w, r, fmt.Sprintf("/entry/%d", e.ID), http.StatusSeeOther)
}

func (s *Server) HandleDeletedEntry(w http.ResponseWriter, r *http.Request) {
	s.templateRenderer.Render(w, "deleted-entry.html", nil)
}

func (s *Server) HandleEditEntryByID(w http.ResponseWriter, r *http.Request) {
	strID := r.PathValue("id")
	id, err := strconv.Atoi(strID)
	if err != nil {
		httpserver.BadRequest(w, r)
		s.logger.Println("Error converting ID to int:", err)

		return
	}

	entryFromDB, err := s.repo.FindByID(r.Context(), id)
	if err != nil {
		httpserver.InternalServerError(w, r)
		s.logger.Println("Error finding entry by ID:", err)

		return
	}

	entry := httpEntryFromEntry(*entryFromDB)

	s.templateRenderer.Render(w, "edit-entry.html", entry)
}

func (s *Server) HandleSubmitEditEntryByID(w http.ResponseWriter, r *http.Request) {
	strID := r.PathValue("id")
	id, err := strconv.Atoi(strID)
	if err != nil {
		httpserver.BadRequest(w, r)
		s.logger.Println("Error converting ID to int:", err)

		return
	}

	title := r.PostFormValue("title")
	date := r.PostFormValue("date")
	content := r.PostFormValue("content")

	if title == "" || date == "" || content == "" {
		httpserver.BadRequest(w, r)
		s.logger.Println("Missing required fields")

		return
	}

	parsedDate, err := time.Parse(dateFormat, date)
	if err != nil {
		httpserver.BadRequest(w, r)
		s.logger.Println("Error parsing date:", err)

		return
	}

	e := &Entry{
		ID:      id,
		Title:   title,
		Date:    parsedDate,
		Content: content,
	}

	err = s.repo.Update(r.Context(), e)
	if err != nil {
		httpserver.InternalServerError(w, r)
		s.logger.Println("Error updating entry:", err)

		return
	}

	http.Redirect(w, r, fmt.Sprintf("/entry/%d", e.ID), http.StatusSeeOther)
}
