package docs

import (
	"database/sql"
	"errors"
	"net/http"
	"time"

	"github.com/go-chi/chi/v5"

	"gitea.parodia.dev/drwily/petal/internal/db"
)

// Version-history tuning.
const (
	// autoSnapshotInterval is the minimum gap between background ('auto')
	// snapshots. Auto-save fires every ~1.5s; without a floor we'd store a
	// version per keystroke-burst. A few minutes keeps history useful for
	// recovery without unbounded growth.
	autoSnapshotInterval = 3 * time.Minute

	// maxAutoVersions caps how many 'auto' snapshots we retain per document.
	// 'manual' and 'pre_restore' versions are never pruned — they're explicit
	// restore points the writer (or a restore) deliberately created.
	maxAutoVersions = 40
)

// versionRoutes registers the history endpoints on the docs sub-router. Paths
// resolve to /api/docs/{id}/versions...
func (h *Handler) versionRoutes(r chi.Router) {
	r.Get("/{id}/versions", h.listVersions)
	r.Post("/{id}/versions", h.createVersion)            // explicit "save a restore point"
	r.Get("/{id}/versions/{vid}", h.getVersion)          // full body for preview
	r.Post("/{id}/versions/{vid}/restore", h.restoreVersion)
}

// listVersions returns the document's snapshots, newest first, without the heavy
// content fields (those load on preview/restore).
func (h *Handler) listVersions(w http.ResponseWriter, r *http.Request) {
	docID := chi.URLParam(r, "id")

	// Scope through the documents table so a snapshot is only visible to the
	// owner of its parent document.
	rows, err := h.DB.Query(
		`SELECT v.id, v.doc_id, v.title, v.word_count, v.kind, v.created_at
		   FROM document_versions v
		   JOIN documents d ON d.id = v.doc_id
		  WHERE v.doc_id = ? AND d.user_id = ?
		  ORDER BY v.created_at DESC`,
		docID, db.LocalUserID,
	)
	if err != nil {
		serverError(w, err)
		return
	}
	defer rows.Close()

	out := []db.DocumentVersion{} // non-nil so an empty history serializes as []
	for rows.Next() {
		var v db.DocumentVersion
		if err := rows.Scan(&v.ID, &v.DocID, &v.Title, &v.WordCount, &v.Kind, &v.CreatedAt); err != nil {
			serverError(w, err)
			return
		}
		out = append(out, v)
	}
	if err := rows.Err(); err != nil {
		serverError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, out)
}

// getVersion returns one snapshot in full (including content) for preview.
func (h *Handler) getVersion(w http.ResponseWriter, r *http.Request) {
	v, err := h.fetchVersion(chi.URLParam(r, "id"), chi.URLParam(r, "vid"))
	if errors.Is(err, sql.ErrNoRows) {
		notFoundMsg(w, "version not found")
		return
	}
	if err != nil {
		serverError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, v)
}

// createVersion takes an explicit, user-requested ('manual') restore point from
// the document's current saved state.
func (h *Handler) createVersion(w http.ResponseWriter, r *http.Request) {
	docID := chi.URLParam(r, "id")

	doc, err := h.fetch(docID)
	if errors.Is(err, sql.ErrNoRows) {
		notFound(w)
		return
	}
	if err != nil {
		serverError(w, err)
		return
	}

	v, err := h.insertVersion(doc, db.VersionKindManual)
	if err != nil {
		serverError(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, v)
}

// restoreVersion copies a snapshot back onto the live document. Before
// overwriting, it captures the current state as a 'pre_restore' version so the
// restore is itself undoable. Returns the restored document.
func (h *Handler) restoreVersion(w http.ResponseWriter, r *http.Request) {
	docID := chi.URLParam(r, "id")
	vid := chi.URLParam(r, "vid")

	v, err := h.fetchVersion(docID, vid)
	if errors.Is(err, sql.ErrNoRows) {
		notFoundMsg(w, "version not found")
		return
	}
	if err != nil {
		serverError(w, err)
		return
	}

	current, err := h.fetch(docID)
	if err != nil {
		serverError(w, err)
		return
	}
	if _, err := h.insertVersion(current, db.VersionKindPreRestore); err != nil {
		serverError(w, err)
		return
	}

	res, err := h.DB.Exec(
		`UPDATE documents
		    SET title = ?, content = ?, content_text = ?, word_count = ?,
		        updated_at = CURRENT_TIMESTAMP
		  WHERE id = ? AND user_id = ?`,
		v.Title, v.Content, v.ContentText, v.WordCount, docID, db.LocalUserID,
	)
	if err != nil {
		serverError(w, err)
		return
	}
	if n, _ := res.RowsAffected(); n == 0 {
		notFound(w)
		return
	}

	doc, err := h.fetch(docID)
	if err != nil {
		serverError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, doc)
}

// maybeAutoSnapshot records a throttled background snapshot of the just-saved
// document. It no-ops when the newest snapshot is younger than
// autoSnapshotInterval, or when the body is unchanged since the last snapshot,
// so an idle or rename-only save costs nothing. Best-effort: callers log but do
// not fail the save if this errors. Prunes old 'auto' versions on success.
func (h *Handler) maybeAutoSnapshot(doc db.Document) error {
	var (
		lastAt   time.Time
		lastText string
		hasPrev  bool
	)
	err := h.DB.QueryRow(
		`SELECT created_at, content_text FROM document_versions
		  WHERE doc_id = ? ORDER BY created_at DESC LIMIT 1`,
		doc.ID,
	).Scan(&lastAt, &lastText)
	switch {
	case errors.Is(err, sql.ErrNoRows):
		hasPrev = false
	case err != nil:
		return err
	default:
		hasPrev = true
	}

	if hasPrev {
		if lastText == doc.ContentText {
			return nil // nothing meaningful changed
		}
		if time.Since(lastAt) < autoSnapshotInterval {
			return nil // too soon; let edits accumulate
		}
	}

	if _, err := h.insertVersion(doc, db.VersionKindAuto); err != nil {
		return err
	}
	return h.pruneAutoVersions(doc.ID)
}

// insertVersion writes a snapshot row of the given kind and returns it (without
// the heavy content fields, matching the list shape).
func (h *Handler) insertVersion(doc db.Document, kind string) (db.DocumentVersion, error) {
	var v db.DocumentVersion
	err := h.DB.QueryRow(
		`INSERT INTO document_versions (doc_id, title, content, content_text, word_count, kind)
		 VALUES (?, ?, ?, ?, ?, ?)
		 RETURNING id, doc_id, title, word_count, kind, created_at`,
		doc.ID, doc.Title, doc.Content, doc.ContentText, doc.WordCount, kind,
	).Scan(&v.ID, &v.DocID, &v.Title, &v.WordCount, &v.Kind, &v.CreatedAt)
	return v, err
}

// pruneAutoVersions trims a document's 'auto' snapshots to the newest
// maxAutoVersions, leaving 'manual' and 'pre_restore' restore points intact.
func (h *Handler) pruneAutoVersions(docID string) error {
	_, err := h.DB.Exec(
		`DELETE FROM document_versions
		  WHERE doc_id = ? AND kind = 'auto'
		    AND id NOT IN (
		        SELECT id FROM document_versions
		         WHERE doc_id = ? AND kind = 'auto'
		         ORDER BY created_at DESC LIMIT ?
		    )`,
		docID, docID, maxAutoVersions,
	)
	return err
}

// fetchVersion loads one full snapshot, scoped to its owner via the parent doc.
func (h *Handler) fetchVersion(docID, vid string) (db.DocumentVersion, error) {
	var v db.DocumentVersion
	err := h.DB.QueryRow(
		`SELECT v.id, v.doc_id, v.title, v.content, v.content_text, v.word_count, v.kind, v.created_at
		   FROM document_versions v
		   JOIN documents d ON d.id = v.doc_id
		  WHERE v.id = ? AND v.doc_id = ? AND d.user_id = ?`,
		vid, docID, db.LocalUserID,
	).Scan(
		&v.ID, &v.DocID, &v.Title, &v.Content, &v.ContentText,
		&v.WordCount, &v.Kind, &v.CreatedAt,
	)
	return v, err
}
