package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"arimelody-web/admin"
	"arimelody-web/api"
	"arimelody-web/global"
	"arimelody-web/templates"
	"arimelody-web/view"

	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"
)

const DEFAULT_PORT int64 = 8080

func main() {
    // initialise database connection
    if env := os.Getenv("ARIMELODY_DB_HOST"); env != "" { global.Config.DB.Host = env }
    if env := os.Getenv("ARIMELODY_DB_NAME"); env != "" { global.Config.DB.Name = env }
    if env := os.Getenv("ARIMELODY_DB_USER"); env != "" { global.Config.DB.User = env }
    if env := os.Getenv("ARIMELODY_DB_PASS"); env != "" { global.Config.DB.Pass = env }
    if global.Config.DB.Host == "" {
        fmt.Fprintf(os.Stderr, "FATAL: db.host not provided! Exiting...\n")
        os.Exit(1)
    }
    if global.Config.DB.Name == "" {
        fmt.Fprintf(os.Stderr, "FATAL: db.name not provided! Exiting...\n")
        os.Exit(1)
    }
    if global.Config.DB.User == "" {
        fmt.Fprintf(os.Stderr, "FATAL: db.user not provided! Exiting...\n")
        os.Exit(1)
    }
    if global.Config.DB.Pass == "" {
        fmt.Fprintf(os.Stderr, "FATAL: db.pass not provided! Exiting...\n")
        os.Exit(1)
    }

    var err error
    global.DB, err = sqlx.Connect(
        "postgres",
        fmt.Sprintf(
            "host=%s user=%s dbname=%s password='%s' sslmode=disable",
            global.Config.DB.Host,
            global.Config.DB.User,
            global.Config.DB.Name,
            global.Config.DB.Pass,
        ),
    )
    if err != nil {
        fmt.Fprintf(os.Stderr, "FATAL: Unable to create database connection pool: %v\n", err)
        os.Exit(1)
    }
    global.DB.SetConnMaxLifetime(time.Minute * 3)
    global.DB.SetMaxOpenConns(10)
    global.DB.SetMaxIdleConns(10)
    defer global.DB.Close()

    // start the web server!
    mux := createServeMux()
    fmt.Printf("Now serving at http://127.0.0.1:%d\n", global.Config.Port)
    log.Fatal(
        http.ListenAndServe(fmt.Sprintf(":%d", global.Config.Port), 
        global.HTTPLog(global.DefaultHeaders(mux)),
    ))
}

func createServeMux() *http.ServeMux {
    mux := http.NewServeMux()
    
    mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler()))
    mux.Handle("/api/", http.StripPrefix("/api", api.Handler()))
    mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler()))
    mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler(filepath.Join(global.Config.DataDirectory, "uploads"))))
    mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Method == http.MethodHead {
            w.WriteHeader(http.StatusOK)
            return
        }

        if r.URL.Path == "/" || r.URL.Path == "/index.html" {
            err := templates.Pages["index"].Execute(w, nil)
            if err != nil {
                http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            }
            return
        }
        staticHandler("public").ServeHTTP(w, r)
    }))
    
    return mux
}

func staticHandler(directory string) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        info, err := os.Stat(filepath.Join(directory, filepath.Clean(r.URL.Path)))

        // does the file exist?
        if err != nil {
            if errors.Is(err, os.ErrNotExist) {
                http.NotFound(w, r)
                return
            }
        }

        // is thjs a directory? (forbidden)
        if info.IsDir() {
            http.NotFound(w, r)
            return
        }

        http.FileServer(http.Dir(directory)).ServeHTTP(w, r)
    })
}
