package config

import (
	"strings"
	"time"

	"github.com/photoprism/photoprism/internal/entity"
	"github.com/photoprism/photoprism/internal/query"
	"github.com/photoprism/photoprism/pkg/colors"
	"github.com/photoprism/photoprism/pkg/txt"
)

// ClientConfig represents HTTP client / Web UI config options.
type ClientConfig struct {
	Mode            string              `json:"mode"`
	Name            string              `json:"name"`
	Version         string              `json:"version"`
	Copyright       string              `json:"copyright"`
	Flags           string              `json:"flags"`
	BaseUri         string              `json:"baseUri"`
	StaticUri       string              `json:"staticUri"`
	CssUri          string              `json:"cssUri"`
	JsUri           string              `json:"jsUri"`
	ManifestUri     string              `json:"manifestUri"`
	ApiUri          string              `json:"apiUri"`
	ContentUri      string              `json:"contentUri"`
	SiteUrl         string              `json:"siteUrl"`
	SiteDomain      string              `json:"siteDomain"`
	SiteAuthor      string              `json:"siteAuthor"`
	SiteTitle       string              `json:"siteTitle"`
	SiteCaption     string              `json:"siteCaption"`
	SiteDescription string              `json:"siteDescription"`
	SitePreview     string              `json:"sitePreview"`
	AppName         string              `json:"appName"`
	AppMode         string              `json:"appMode"`
	AppIcon         string              `json:"appIcon"`
	Debug           bool                `json:"debug"`
	Test            bool                `json:"test"`
	Demo            bool                `json:"demo"`
	Sponsor         bool                `json:"sponsor"`
	ReadOnly        bool                `json:"readonly"`
	UploadNSFW      bool                `json:"uploadNSFW"`
	Public          bool                `json:"public"`
	Experimental    bool                `json:"experimental"`
	AlbumCategories []string            `json:"albumCategories"`
	Albums          entity.Albums       `json:"albums"`
	Cameras         entity.Cameras      `json:"cameras"`
	Lenses          entity.Lenses       `json:"lenses"`
	Countries       entity.Countries    `json:"countries"`
	People          entity.People       `json:"people"`
	Thumbs          ThumbSizes          `json:"thumbs"`
	Status          string              `json:"status"`
	MapKey          string              `json:"mapKey"`
	DownloadToken   string              `json:"downloadToken"`
	PreviewToken    string              `json:"previewToken"`
	Settings        Settings            `json:"settings"`
	Disable         ClientDisable       `json:"disable"`
	Count           ClientCounts        `json:"count"`
	Pos             ClientPosition      `json:"pos"`
	Years           Years               `json:"years"`
	Colors          []map[string]string `json:"colors"`
	Categories      CategoryLabels      `json:"categories"`
	Clip            int                 `json:"clip"`
	Server          RuntimeInfo         `json:"server"`
}

// Years represents a list of years.
type Years []int

// ClientDisable represents disabled client features a user cannot turn back on.
type ClientDisable struct {
	Backups        bool `json:"backups"`
	WebDAV         bool `json:"webdav"`
	Settings       bool `json:"settings"`
	Places         bool `json:"places"`
	ExifTool       bool `json:"exiftool"`
	FFmpeg         bool `json:"ffmpeg"`
	Darktable      bool `json:"darktable"`
	Rawtherapee    bool `json:"rawtherapee"`
	Sips           bool `json:"sips"`
	HeifConvert    bool `json:"heifconvert"`
	TensorFlow     bool `json:"tensorflow"`
	Faces          bool `json:"faces"`
	Classification bool `json:"classification"`
}

// ClientCounts represents photo, video and album counts for the client UI.
type ClientCounts struct {
	All            int `json:"all"`
	Photos         int `json:"photos"`
	Live           int `json:"live"`
	Videos         int `json:"videos"`
	Cameras        int `json:"cameras"`
	Lenses         int `json:"lenses"`
	Countries      int `json:"countries"`
	Hidden         int `json:"hidden"`
	Favorites      int `json:"favorites"`
	Private        int `json:"private"`
	Review         int `json:"review"`
	Stories        int `json:"stories"`
	Albums         int `json:"albums"`
	Moments        int `json:"moments"`
	Months         int `json:"months"`
	Folders        int `json:"folders"`
	Files          int `json:"files"`
	People         int `json:"people"`
	Places         int `json:"places"`
	States         int `json:"states"`
	Labels         int `json:"labels"`
	LabelMaxPhotos int `json:"labelMaxPhotos"`
}

type CategoryLabels []CategoryLabel

type CategoryLabel struct {
	LabelUID   string `json:"UID"`
	CustomSlug string `json:"Slug"`
	LabelName  string `json:"Name"`
}

type ClientPosition struct {
	PhotoUID string    `json:"uid"`
	CellID   string    `json:"cid"`
	TakenAt  time.Time `json:"utc"`
	PhotoLat float64   `json:"lat"`
	PhotoLng float64   `json:"lng"`
}

// Flags returns config flags as string slice.
func (c *Config) Flags() (flags []string) {
	if c.Public() {
		flags = append(flags, "public")
	}

	if c.Debug() {
		flags = append(flags, "debug")
	}

	if c.Sponsor() {
		flags = append(flags, "sponsor")
	}

	if c.Experimental() {
		flags = append(flags, "experimental")
	}

	if c.ReadOnly() {
		flags = append(flags, "readonly")
	}

	if !c.DisableSettings() {
		flags = append(flags, "settings")
	}

	if !c.Settings().UI.Scrollbar {
		flags = append(flags, "hide-scrollbar")
	}

	return flags
}

// PublicConfig returns public client config options with as little information as possible.
func (c *Config) PublicConfig() ClientConfig {
	if c.Public() {
		return c.UserConfig()
	}

	assets := c.ClientAssets()
	settings := c.Settings()

	result := ClientConfig{
		Settings: Settings{
			UI:       settings.UI,
			Maps:     settings.Maps,
			Features: settings.Features,
			Share:    settings.Share,
		},
		Disable: ClientDisable{
			Backups:        true,
			WebDAV:         true,
			Settings:       c.DisableSettings(),
			Places:         c.DisablePlaces(),
			ExifTool:       true,
			FFmpeg:         true,
			Darktable:      true,
			Rawtherapee:    true,
			Sips:           true,
			HeifConvert:    true,
			TensorFlow:     true,
			Faces:          true,
			Classification: true,
		},
		Flags:           strings.Join(c.Flags(), " "),
		Mode:            "public",
		Name:            c.Name(),
		BaseUri:         c.BaseUri(""),
		StaticUri:       c.StaticUri(),
		CssUri:          assets.AppCssUri(),
		JsUri:           assets.AppJsUri(),
		ApiUri:          c.ApiUri(),
		ContentUri:      c.ContentUri(),
		SiteUrl:         c.SiteUrl(),
		SiteDomain:      c.SiteDomain(),
		SiteAuthor:      c.SiteAuthor(),
		SiteTitle:       c.SiteTitle(),
		SiteCaption:     c.SiteCaption(),
		SiteDescription: c.SiteDescription(),
		SitePreview:     c.SitePreview(),
		AppName:         c.AppName(),
		AppMode:         c.AppMode(),
		AppIcon:         c.AppIcon(),
		Version:         c.Version(),
		Copyright:       c.Copyright(),
		Debug:           c.Debug(),
		Test:            c.Test(),
		Demo:            c.Demo(),
		Sponsor:         c.Sponsor(),
		ReadOnly:        c.ReadOnly(),
		Public:          c.Public(),
		Experimental:    c.Experimental(),
		Status:          "",
		MapKey:          "",
		Thumbs:          Thumbs,
		Colors:          colors.All.List(),
		ManifestUri:     c.ClientManifestUri(),
		Clip:            txt.ClipDefault,
		PreviewToken:    "public",
		DownloadToken:   "public",
	}

	return result
}

// GuestConfig returns client config options for the sharing with guests.
func (c *Config) GuestConfig() ClientConfig {
	assets := c.ClientAssets()
	settings := c.Settings()

	result := ClientConfig{
		Settings: Settings{
			UI:       settings.UI,
			Maps:     settings.Maps,
			Features: settings.Features,
			Share:    settings.Share,
		},
		Disable: ClientDisable{
			Backups:        true,
			WebDAV:         c.DisableWebDAV(),
			Settings:       c.DisableSettings(),
			Places:         c.DisablePlaces(),
			ExifTool:       true,
			FFmpeg:         true,
			Darktable:      true,
			Rawtherapee:    true,
			Sips:           true,
			HeifConvert:    true,
			TensorFlow:     true,
			Faces:          true,
			Classification: true,
		},
		Flags:           strings.Join(c.Flags(), " "),
		Mode:            "guest",
		Name:            c.Name(),
		BaseUri:         c.BaseUri(""),
		StaticUri:       c.StaticUri(),
		CssUri:          assets.ShareCssUri(),
		JsUri:           assets.ShareJsUri(),
		ApiUri:          c.ApiUri(),
		ContentUri:      c.ContentUri(),
		SiteUrl:         c.SiteUrl(),
		SiteDomain:      c.SiteDomain(),
		SiteAuthor:      c.SiteAuthor(),
		SiteTitle:       c.SiteTitle(),
		SiteCaption:     c.SiteCaption(),
		SiteDescription: c.SiteDescription(),
		SitePreview:     c.SitePreview(),
		AppName:         c.AppName(),
		AppMode:         c.AppMode(),
		AppIcon:         c.AppIcon(),
		Version:         c.Version(),
		Copyright:       c.Copyright(),
		Debug:           c.Debug(),
		Test:            c.Test(),
		Demo:            c.Demo(),
		Sponsor:         c.Sponsor(),
		ReadOnly:        true,
		UploadNSFW:      c.UploadNSFW(),
		Public:          true,
		Experimental:    false,
		Colors:          colors.All.List(),
		Thumbs:          Thumbs,
		Status:          c.Hub().Status,
		MapKey:          c.Hub().MapKey(),
		DownloadToken:   c.DownloadToken(),
		PreviewToken:    c.PreviewToken(),
		ManifestUri:     c.ClientManifestUri(),
		Clip:            txt.ClipDefault,
	}

	return result
}

// UserConfig returns client configuration options for registered users.
func (c *Config) UserConfig() ClientConfig {
	assets := c.ClientAssets()

	result := ClientConfig{
		Settings: *c.Settings(),
		Disable: ClientDisable{
			Backups:        c.DisableBackups(),
			WebDAV:         c.DisableWebDAV(),
			Settings:       c.DisableSettings(),
			Places:         c.DisablePlaces(),
			ExifTool:       c.DisableExifTool(),
			FFmpeg:         c.DisableFFmpeg(),
			Darktable:      c.DisableDarktable(),
			Rawtherapee:    c.DisableRawtherapee(),
			Sips:           c.DisableSips(),
			HeifConvert:    c.DisableHeifConvert(),
			TensorFlow:     c.DisableTensorFlow(),
			Faces:          c.DisableFaces(),
			Classification: c.DisableClassification(),
		},
		Flags:           strings.Join(c.Flags(), " "),
		Mode:            "user",
		Name:            c.Name(),
		BaseUri:         c.BaseUri(""),
		StaticUri:       c.StaticUri(),
		CssUri:          assets.AppCssUri(),
		JsUri:           assets.AppJsUri(),
		ApiUri:          c.ApiUri(),
		ContentUri:      c.ContentUri(),
		SiteUrl:         c.SiteUrl(),
		SiteDomain:      c.SiteDomain(),
		SiteAuthor:      c.SiteAuthor(),
		SiteTitle:       c.SiteTitle(),
		SiteCaption:     c.SiteCaption(),
		SiteDescription: c.SiteDescription(),
		SitePreview:     c.SitePreview(),
		AppName:         c.AppName(),
		AppMode:         c.AppMode(),
		AppIcon:         c.AppIcon(),
		Version:         c.Version(),
		Copyright:       c.Copyright(),
		Debug:           c.Debug(),
		Test:            c.Test(),
		Demo:            c.Demo(),
		Sponsor:         c.Sponsor(),
		ReadOnly:        c.ReadOnly(),
		UploadNSFW:      c.UploadNSFW(),
		Public:          c.Public(),
		Experimental:    c.Experimental(),
		Colors:          colors.All.List(),
		Thumbs:          Thumbs,
		Status:          c.Hub().Status,
		MapKey:          c.Hub().MapKey(),
		DownloadToken:   c.DownloadToken(),
		PreviewToken:    c.PreviewToken(),
		ManifestUri:     c.ClientManifestUri(),
		Clip:            txt.ClipDefault,
		Server:          NewRuntimeInfo(),
	}

	c.Db().
		Table("photos").
		Select("photo_uid, cell_id, photo_lat, photo_lng, taken_at").
		Where("deleted_at IS NULL AND photo_lat <> 0 AND photo_lng <> 0").
		Order("taken_at DESC").
		Limit(1).Offset(0).
		Take(&result.Pos)

	c.Db().
		Table("cameras").
		Where("camera_slug <> 'zz' AND camera_slug <> ''").
		Select("COUNT(*) AS cameras").
		Take(&result.Count)

	c.Db().
		Table("lenses").
		Where("lens_slug <> 'zz' AND lens_slug <> ''").
		Select("COUNT(*) AS lenses").
		Take(&result.Count)

	c.Db().
		Table("photos").
		Select("SUM(photo_type = 'video' AND photo_quality >= 0 AND photo_private = 0) AS videos, " +
			"SUM(photo_type = 'live' AND photo_quality >= 0 AND photo_private = 0) AS live, " +
			"SUM(photo_quality = -1) AS hidden, SUM(photo_type IN ('image','raw') AND photo_private = 0 AND photo_quality >= 0) AS photos, " +
			"SUM(photo_type IN ('image','raw','live') AND photo_quality < 3 AND photo_quality >= 0 AND photo_private = 0) AS review, " +
			"SUM(photo_favorite = 1 AND photo_private = 0 AND photo_quality >= 0) AS favorites, " +
			"SUM(photo_private = 1 AND photo_quality >= 0) AS private").
		Where("photos.id NOT IN (SELECT photo_id FROM files WHERE file_primary = 1 AND (file_missing = 1 OR file_error <> ''))").
		Where("deleted_at IS NULL").
		Take(&result.Count)

	result.Count.All = result.Count.Photos + result.Count.Live + result.Count.Videos

	c.Db().
		Table("labels").
		Select("MAX(photo_count) AS label_max_photos, COUNT(*) AS labels").
		Where("photo_count > 0").
		Where("deleted_at IS NULL").
		Where("(label_priority >= 0 OR label_favorite = 1)").
		Take(&result.Count)

	c.Db().
		Table("albums").
		Select("SUM(album_type = ?) AS albums, SUM(album_type = ?) AS moments, SUM(album_type = ?) AS months, SUM(album_type = ?) AS states, SUM(album_type = ?) AS folders", entity.AlbumDefault, entity.AlbumMoment, entity.AlbumMonth, entity.AlbumState, entity.AlbumFolder).
		Where("deleted_at IS NULL AND (albums.album_type <> 'folder' OR albums.album_path IN (SELECT photos.photo_path FROM photos WHERE photos.deleted_at IS NULL))").
		Take(&result.Count)

	c.Db().
		Table("files").
		Select("COUNT(*) AS files").
		Where("file_missing = 0").
		Where("deleted_at IS NULL").
		Take(&result.Count)

	c.Db().
		Table("countries").
		Select("(COUNT(*) - 1) AS countries").
		Take(&result.Count)

	c.Db().
		Table("places").
		Select("SUM(photo_count > 0) AS places").
		Where("id <> 'zz'").
		Take(&result.Count)

	c.Db().
		Order("country_slug").
		Find(&result.Countries)

	// People are subjects with type person.
	result.Count.People, _ = query.PeopleCount()
	result.People, _ = query.People()

	c.Db().
		Where("id IN (SELECT photos.camera_id FROM photos WHERE photos.photo_quality >= 0 OR photos.deleted_at IS NULL)").
		Where("deleted_at IS NULL").
		Limit(10000).Order("camera_slug").
		Find(&result.Cameras)

	c.Db().
		Where("deleted_at IS NULL").
		Limit(10000).Order("lens_slug").
		Find(&result.Lenses)

	c.Db().
		Where("deleted_at IS NULL AND album_favorite = 1").
		Limit(20).Order("album_title").
		Find(&result.Albums)

	c.Db().
		Table("photos").
		Where("photo_year > 0 AND (photos.photo_quality >= 0 OR photos.deleted_at IS NULL)").
		Order("photo_year DESC").
		Pluck("DISTINCT photo_year", &result.Years)

	c.Db().
		Table("categories").
		Select("l.label_uid, l.custom_slug, l.label_name").
		Joins("JOIN labels l ON categories.category_id = l.id").
		Where("l.deleted_at IS NULL").
		Group("l.custom_slug").
		Order("l.custom_slug").
		Limit(1000).Offset(0).
		Scan(&result.Categories)

	c.Db().
		Table("albums").
		Select("album_category").
		Where("deleted_at IS NULL AND album_category <> ''").
		Group("album_category").
		Order("album_category").
		Limit(1000).Offset(0).
		Pluck("album_category", &result.AlbumCategories)

	return result
}
