package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"log/slog"
	"net/http"
	"net/http/httputil"
	"os"
	"os/exec"
	"path"
	"time"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/joho/godotenv"
	"gopkg.in/natefinch/lumberjack.v2"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type GormModel struct {
	ID        uint           `gorm:"primarykey" json:"id"`
	CreatedAt time.Time      `json:"createdAt"`
	UpdatedAt time.Time      `json:"updatedAt"`
	DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
}

// Types of media supported by Watcharr
// in an overarching way.
type SupportedMedia string

const (
	SupportedMediaMovie SupportedMedia = "movie"
	SupportedMediaShow  SupportedMedia = "tv"
	SupportedMediaGame  SupportedMedia = "game"
)

var (
	ServerInSetup = false
	logLevel      = new(slog.LevelVar)
)

func main() {
	err := godotenv.Load()
	if err != nil {
		// Do not fail if file does not exist
		if !os.IsNotExist(err) {
			log.Fatal("Failed to load vars from .env file:", err)
		}
	}

	multiw := setupLogging()
	slog.Info("Watcharr Starting")

	if err = readConfig(); err != nil {
		log.Fatal("Failed to read server config!", err)
	}

	setLoggingLevel()

	// Ensure data dir exists
	err = ensureDirExists(DataPath)
	if err != nil {
		log.Fatal("Failed to create data dir:", err)
	}

	// Check if we want to be in DEV or PROD
	isProd := true
	if os.Getenv("MODE") == "DEV" {
		slog.Info("Starting in DEV mode")
		isProd = false
	}

	db, err := gorm.Open(sqlite.Open(path.Join(DataPath, "watcharr.db")), &gorm.Config{TranslateError: true})
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}

	err = db.AutoMigrate(
		&User{},
		&UserServices{},
		&Content{},
		&Watched{},
		&WatchedSeason{},
		&WatchedEpisode{},
		&Activity{},
		&Token{},
		&Follow{},
		&Image{},
		&Game{},
		&ArrRequest{},
		&Tag{},
	)
	if err != nil {
		log.Fatal("Failed to auto migrate database:", err)
	}

	if isProd {
		go runUI()
		gin.SetMode(gin.ReleaseMode)
	}
	gin.DefaultWriter = multiw
	gine := gin.Default()
	gine.Use(cors.New(cors.Config{
		AllowOrigins: []string{"*"},
		AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowHeaders: []string{
			"Content-Type",
			"Content-Length",
			"Accept-Encoding",
			"X-CSRF-Token",
			"Authorization",
			"accept",
			"origin",
			"Cache-Control",
			"X-Requested-With",
		},
		ExposeHeaders: []string{
			"Content-Length",
			"watcharr-lastviewedseason-saved",
		},
		AllowCredentials: true,
		MaxAge:           12 * time.Hour,
	}))
	if isProd {
		// Proxy NoRoute requests to UI server
		gine.NoRoute(func(c *gin.Context) {
			director := func(req *http.Request) {
				req.URL.Scheme = "http"
				req.URL.Host = "127.0.0.1:3000"
			}
			proxy := &httputil.ReverseProxy{Director: director}
			proxy.ServeHTTP(c.Writer, c.Request)
		})
	}
	br := newBaseRouter(db, gine.Group("/api"))
	// Only add setup routes if there are no users found in db.
	var userCount int64
	if uresp := db.Model(&User{}).Count(&userCount); uresp.Error == nil {
		if userCount != 0 {
			slog.Debug("registered users found.. skipped creating setup routes.")
		} else {
			slog.Info("No users found.. creating setup routes.")
			ServerInSetup = true
			br.addSetupRoutes()
		}
	} else {
		slog.Error("Failed to check if any users exist.. not registering setup routes", "error", uresp.Error)
	}
	br.addAuthRoutes()
	br.addContentRoutes()
	br.addGameRoutes()
	br.addWatchedRoutes()
	br.addActivityRoutes()
	br.addProfileRoutes()
	br.addJellyfinRoutes()
	br.addPlexRoutes()
	br.addUserRoutes()
	br.addFollowRoutes()
	br.addImportRoutes()
	br.addServerRoutes()
	br.addFeatureRoutes()
	br.addSonarrRoutes()
	br.addRadarrRoutes()
	br.addArrRequestRoutes()
	br.addJobRoutes()
	br.addTaskRoutes()
	br.addTagRoutes()
	br.rg.Static("/img", path.Join(DataPath, "img"))

	go setupTasks(db)

	gine.Run("0.0.0.0:3080")
}

// Setup slog defaults
func setupLogging() io.Writer {
	// logLevel = new(slog.LevelVar)
	multiw := io.MultiWriter(&lumberjack.Logger{
		Filename:   path.Join(DataPath, "watcharr.log"),
		MaxSize:    1, // megabytes
		MaxBackups: 3,
		MaxAge:     28, // days
		Compress:   false,
	}, os.Stdout)
	slog.SetDefault(slog.New(
		slog.NewTextHandler(multiw, &slog.HandlerOptions{Level: logLevel}),
	))
	return multiw
}

// Set loggin level from config
func setLoggingLevel() {
	if Config.DEBUG {
		logLevel.Set(slog.LevelDebug)
	} else {
		logLevel.Set(slog.LevelInfo)
	}
	slog.Info("Logging level set", "logging_level", logLevel)
}

// Run UI server
func runUI() {
	cmd := exec.Command("node", "ui/index.js")
	cmdReader, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal("UI ERR @ get stdout read pipe: ", err)
	}
	scanner := bufio.NewScanner(cmdReader)
	go func() {
		for scanner.Scan() {
			fmt.Println(scanner.Text())
		}
	}()
	if err := cmd.Start(); err != nil {
		log.Fatal("UI ERR @ command start: ", err)
	}
	if err := cmd.Wait(); err != nil {
		log.Fatal("UI ERR @ command wait: ", err)
	}
}

func ensureDirExists(dir string) error {
	err := os.MkdirAll(dir, 0764)
	if err != nil {
		return err
	}
	return nil
}
