package main

import (
	"database/sql"
	"embed"
	"flag"
	"fmt"
	"html/template"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"

	_ "github.com/mattn/go-sqlite3"
)

var imageExtensions = map[string]bool{
	".png":  true,
	".jpg":  true,
	".jpeg": true,
	// ".webp": true,
	".bmp": true,
}

func isImageFile(path string) bool {
	ext := strings.ToLower(filepath.Ext(path))
	return imageExtensions[ext]
}

//go:embed templates/*
var templatesFS embed.FS

//go:embed assets/*
var assetsFS embed.FS

type CoverData struct {
	Comic int
	Path  string
}

var (
	db           *sql.DB
	tmpl         *template.Template
	imaginaryURL string
	galleryPath  string
	databasePath string
	listenAddr   string
	listenPort   int
	randomCovers []CoverData
)

func main() {
	flag.StringVar(&imaginaryURL, "imaginary", "http://192.168.88.54:10001", "imaginary root URL")
	flag.StringVar(&galleryPath, "gallery", "/home/user/mnt/panda/galleries/", "gallery path")
	flag.StringVar(&databasePath, "database", "./db.sqlite", "database path")
	flag.StringVar(&listenAddr, "address", "localhost", "listen address")
	flag.IntVar(&listenPort, "port", 10000, "listen port")
	flag.Parse()

	var err error

	db, err = sql.Open("sqlite3", databasePath)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	if err = db.Ping(); err != nil {
		log.Fatal(err)
	}
	log.Println("SQLite connected")

	tmpl, err = template.ParseFS(templatesFS, "templates/*.html")
	if err != nil {
		log.Fatal(err)
	}

	http.HandleFunc("/gallery/", handleGallery)
	http.HandleFunc("/random/", handleRandom)
	http.HandleFunc("/random", handleRandom)
	http.HandleFunc("/", handleIndex)
	http.Handle("/assets/", http.FileServer(http.FS(assetsFS)))
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(galleryPath))))

	addr := fmt.Sprintf("%s:%d", listenAddr, listenPort)
	log.Printf("Server listening on %s", addr)
	log.Fatal(http.ListenAndServe(addr, nil))
}

func handleRandom(w http.ResponseWriter, r *http.Request) {
	path := strings.TrimPrefix(r.URL.Path, "/random")
	path = strings.TrimPrefix(path, "/")

	// If accessing /random without chapter, shuffle and redirect to /random/0
	if path == "" {
		query := `
			select page.id_comic, comic.directory || '/' || page.filename path
			from page
			join comic on comic.id_comic = page.id_comic
			where page.number = 0
		`
		rows, err := db.Query(query)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer rows.Close()

		randomCovers = nil
		for rows.Next() {
			var c CoverData
			if err := rows.Scan(&c.Comic, &c.Path); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			randomCovers = append(randomCovers, c)
		}

		rand.Shuffle(len(randomCovers), func(i, j int) {
			randomCovers[i], randomCovers[j] = randomCovers[j], randomCovers[i]
		})

		http.Redirect(w, r, "/random/0", http.StatusFound)
		return
	}

	chapter, err := strconv.Atoi(path)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	const limit = 18
	offset := chapter * limit

	type Cover struct {
		Comic int
		Url   string
	}

	end := offset + limit*2
	if end > len(randomCovers) {
		end = len(randomCovers)
	}
	start := offset
	if start > len(randomCovers) {
		start = len(randomCovers)
	}

	all := make([]Cover, 0, limit*2)
	for _, c := range randomCovers[start:end] {
		var coverUrl string
		if isImageFile(c.Path) {
			coverUrl = fmt.Sprintf("%s/smartcrop?width=300&height=370&file=%s", imaginaryURL, url.QueryEscape(c.Path))
		} else {
			coverUrl = "/static/" + c.Path
		}
		all = append(all, Cover{Comic: c.Comic, Url: coverUrl})
	}

	covers := all
	var preload []Cover
	if len(all) > limit {
		covers = all[:limit]
		preload = all[limit:]
	}

	var nextChapter int
	if len(preload) > 0 {
		nextChapter = chapter + 1
	} else {
		nextChapter = chapter
	}

	data := struct {
		Covers  []Cover
		Preload []Cover
		Chapter int
		Prefix  string
	}{covers, preload, nextChapter, "/random"}

	tmpl.ExecuteTemplate(w, "index.html", data)
}

func handleGallery(w http.ResponseWriter, r *http.Request) {
	comic := strings.TrimPrefix(r.URL.Path, "/gallery/")
	if comic == "" {
		http.NotFound(w, r)
		return
	}

	query := `
		select comic.title, comic.directory || '/' || page.filename
		from page
		join comic on page.id_comic = comic.id_comic
		where comic.id_comic = ?
		order by page.number
	`
	rows, err := db.Query(query, comic)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	var title string
	var pages []string
	for rows.Next() {
		var path string
		if err := rows.Scan(&title, &path); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		pages = append(pages, path)
	}

	if len(pages) == 0 {
		http.NotFound(w, r)
		return
	}

	data := struct {
		Title        string
		Pages        []string
		ImaginaryURL string
	}{title, pages, imaginaryURL}

	tmpl.ExecuteTemplate(w, "gallery.html", data)
}

func handleIndex(w http.ResponseWriter, r *http.Request) {
	chapter := 0
	path := strings.TrimPrefix(r.URL.Path, "/")
	if path != "" {
		var err error
		chapter, err = strconv.Atoi(path)
		if err != nil {
			http.NotFound(w, r)
			return
		}
	}

	const limit = 18
	offset := chapter * limit

	query := `
        select page.id_comic, comic.directory || '/' || page.filename path
        from page
        join comic on comic.id_comic = page.id_comic
        where page.number = 0
		order by comic.id_comic desc
        limit ? offset ?
	`

	rows, err := db.Query(query, limit*2, offset)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	type Cover struct {
		Comic int
		Url   string
	}

	all := make([]Cover, 0, limit*2)
	for rows.Next() {
		var comic int
		var path string
		if err := rows.Scan(&comic, &path); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		var coverUrl string
		if isImageFile(path) {
			coverUrl = fmt.Sprintf("%s/smartcrop?width=300&height=370&file=%s", imaginaryURL, url.QueryEscape(path))
		} else {
			coverUrl = "/static/" + path
		}
		all = append(all, Cover{Comic: comic, Url: coverUrl})
	}

	covers := all
	var preload []Cover
	if len(all) > limit {
		covers = all[:limit]
		preload = all[limit:]
	}

	var nextChapter int
	if len(preload) > 0 {
		nextChapter = chapter + 1
	} else {
		nextChapter = chapter
	}
	data := struct {
		Covers  []Cover
		Preload []Cover
		Chapter int
		Prefix  string
	}{covers, preload, nextChapter, ""}

	tmpl.ExecuteTemplate(w, "index.html", data)
}
