package httpapi

import (
	"context"
	"time"

	"git.azzamo.net/Michilis/Spanglish/photo-api/internal/store"
)

// JSON shapes are camelCase like the backend's responses.

type eventJSON struct {
	ID            string `json:"id"`
	Slug          string `json:"slug"`
	Title         string `json:"title"`
	TitleEs       string `json:"titleEs,omitempty"`
	StartDatetime string `json:"startDatetime"`
	Status        string `json:"status"`
}

type galleryJSON struct {
	ID            string     `json:"id"`
	Slug          string     `json:"slug"`
	Title         string     `json:"title"`
	TitleEs       string     `json:"titleEs,omitempty"`
	Description   string     `json:"description,omitempty"`
	DescriptionEs string     `json:"descriptionEs,omitempty"`
	EventID       string     `json:"eventId,omitempty"`
	Visibility    string     `json:"visibility"`
	CoverPhotoID  string     `json:"coverPhotoId,omitempty"`
	PhotoCount    int        `json:"photoCount"`
	CoverURL      string     `json:"coverUrl,omitempty"`
	CreatedAt     string     `json:"createdAt"`
	UpdatedAt     string     `json:"updatedAt"`
	Event         *eventJSON `json:"event,omitempty"`
	// Admin-only fields:
	ShareToken string `json:"shareToken,omitempty"`
	ShareURL   string `json:"shareUrl,omitempty"`
}

type photoURLs struct {
	Thumb    string `json:"thumb,omitempty"`
	Preview  string `json:"preview,omitempty"`
	Original string `json:"original"`
	// Download / DownloadOriginal hit the download endpoint (files.go), which
	// always streams the bytes same-origin instead of redirecting to S3, so a
	// fetch() can read them into a Blob without CORS. Download serves the
	// preview variant and doubles as the lightbox's <img> source, so the save
	// fetch is answered from the HTTP cache rather than the network.
	Download         string `json:"download,omitempty"`
	DownloadOriginal string `json:"downloadOriginal"`
}

type photoJSON struct {
	ID               string    `json:"id"`
	GalleryID        string    `json:"galleryId"`
	Position         int       `json:"position"`
	OriginalFilename string    `json:"originalFilename,omitempty"`
	ContentType      string    `json:"contentType"`
	SizeBytes        int64     `json:"sizeBytes"`
	// PreviewSizeBytes lets the mobile save sheet label both of its rows with
	// a real file size without fetching anything. Omitted while unknown.
	PreviewSizeBytes int64     `json:"previewSizeBytes,omitempty"`
	Width            int       `json:"width,omitempty"`
	Height           int       `json:"height,omitempty"`
	TakenAt          string    `json:"takenAt,omitempty"`
	Status           string    `json:"status"`
	LastError        string    `json:"lastError,omitempty"` // admin only
	CreatedAt        string    `json:"createdAt"`
	URLs             photoURLs `json:"urls"`
	// Duplicate marks an upload that was skipped because the gallery already
	// held these bytes; the rest of the object describes the existing photo.
	// Set by the upload handler only, never persisted.
	Duplicate bool `json:"duplicate,omitempty"`
}

// viewTokenFor returns the token to embed in a gallery's file URLs: none
// for public galleries (files are anonymously accessible), a short-lived
// gallery-scoped view token otherwise (see viewtoken.go).
func (s *Server) viewTokenFor(g store.Gallery) string {
	if g.Visibility == store.VisibilityPublic {
		return ""
	}
	return mintViewToken([]byte(s.cfg.ViewTokenSecret), g.ID)
}

func isoTime(t time.Time) string {
	if t.IsZero() {
		return ""
	}
	return t.UTC().Format("2006-01-02T15:04:05.000Z")
}

// fileURL builds the access-checked file endpoint URL; token is appended
// for link-mode viewers so the client can use URLs verbatim.
func fileURL(photoID, variant, token string) string {
	u := "/api/photos/files/" + photoID + "/" + variant
	if token != "" {
		u += "?token=" + token
	}
	return u
}

// downloadURL builds the download endpoint's URL. size is left off for the
// preview, which the endpoint serves by default — a shorter, stabler string
// for the URL the lightbox also renders.
func downloadURL(photoID, size, token string) string {
	u := "/api/photos/files/" + photoID + "/download"
	sep := "?"
	if size != "" && size != downloadSizePreview {
		u += "?size=" + size
		sep = "&"
	}
	if token != "" {
		u += sep + "token=" + token
	}
	return u
}

func (s *Server) photoToJSON(p store.Photo, token string, admin bool) photoJSON {
	out := photoJSON{
		ID:               p.ID,
		GalleryID:        p.GalleryID,
		Position:         p.Position,
		OriginalFilename: p.OriginalFilename,
		ContentType:      p.ContentType,
		SizeBytes:        p.SizeBytes,
		Width:            p.Width,
		Height:           p.Height,
		PreviewSizeBytes: p.PreviewSizeBytes,
		TakenAt:          isoTime(p.TakenAt),
		Status:           p.Status,
		CreatedAt:        isoTime(p.CreatedAt),
		URLs: photoURLs{
			Original:         fileURL(p.ID, "original", token),
			DownloadOriginal: downloadURL(p.ID, downloadSizeOriginal, token),
		},
	}
	if p.ThumbKey != "" {
		out.URLs.Thumb = fileURL(p.ID, "thumb", token)
	}
	if p.PreviewKey != "" {
		out.URLs.Preview = fileURL(p.ID, "preview", token)
		out.URLs.Download = downloadURL(p.ID, downloadSizePreview, token)
	}
	if admin {
		out.LastError = p.LastError
	}
	return out
}

func (s *Server) galleryToJSON(ctx context.Context, g store.Gallery, token string, admin bool) galleryJSON {
	out := galleryJSON{
		ID:            g.ID,
		Slug:          g.Slug,
		Title:         g.Title,
		TitleEs:       g.TitleEs,
		Description:   g.Description,
		DescriptionEs: g.DescriptionEs,
		EventID:       g.EventID,
		Visibility:    g.Visibility,
		CoverPhotoID:  g.CoverPhotoID,
		PhotoCount:    g.PhotoCount,
		CreatedAt:     isoTime(g.CreatedAt),
		UpdatedAt:     isoTime(g.UpdatedAt),
	}
	if coverID, _ := s.db.GalleryCoverKey(ctx, g); coverID != "" {
		out.CoverURL = fileURL(coverID, "thumb", token)
	}
	if g.EventID != "" {
		if ev, err := s.db.GetEventSummary(ctx, g.EventID); err == nil {
			out.Event = &eventJSON{
				ID:            ev.ID,
				Slug:          ev.Slug,
				Title:         ev.Title,
				TitleEs:       ev.TitleEs,
				StartDatetime: isoTime(ev.StartDatetime),
				Status:        ev.Status,
			}
		}
	}
	if admin {
		out.ShareToken = g.ShareToken
		// Event-linked galleries live under the event's URL; standalone
		// galleries keep their own /photos/<slug> URL.
		page := "/photos/" + g.Slug
		if out.Event != nil {
			page = "/events/" + out.Event.Slug + "/gallery"
		}
		out.ShareURL = s.cfg.FrontendURL + page
		// The token only grants anything in link/ticket mode; public and
		// private galleries get a clean URL.
		if g.Visibility == store.VisibilityLink || g.Visibility == store.VisibilityTicket {
			out.ShareURL += "?token=" + g.ShareToken
		}
	}
	return out
}
