package main

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"os"
	"strconv"

	"github.com/otaxhu/problem"
	"github.com/zoriya/kyoo/keibi/dbc"
	_ "github.com/zoriya/kyoo/keibi/docs"

	"github.com/go-playground/validator/v10"
	"github.com/golang-migrate/migrate/v4"
	pgxd "github.com/golang-migrate/migrate/v4/database/pgx/v5"
	_ "github.com/golang-migrate/migrate/v4/source/file"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/jackc/pgx/v5/stdlib"
	"github.com/labstack/echo-jwt/v4"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/swaggo/echo-swagger"
)

func ErrorHandler(err error, c echo.Context) {
	code := http.StatusInternalServerError
	var message string

	if he, ok := err.(*echo.HTTPError); ok {
		code = he.Code
		message = fmt.Sprint(he.Message)
	} else {
		c.Logger().Error(err)
	}

	ret := problem.NewMap(code, message)
	c.JSON(code, ret)
}

type Validator struct {
	validator *validator.Validate
}

func (v *Validator) Validate(i interface{}) error {
	if err := v.validator.Struct(i); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
	}
	return nil
}

func (h *Handler) CheckHealth(c echo.Context) error {
	return c.JSON(200, struct{ Status string }{Status: "healthy"})
}

func GetenvOr(env string, def string) string {
	out := os.Getenv(env)
	if out == "" {
		return def
	}
	return out
}

func OpenDatabase() (*pgxpool.Pool, error) {
	ctx := context.Background()

	port, err := strconv.ParseUint(GetenvOr("POSTGRES_PORT", "5432"), 10, 16)
	if err != nil {
		return nil, errors.New("invalid postgres port specified")
	}

	config, _ := pgxpool.ParseConfig("")
	config.ConnConfig.Host = GetenvOr("POSTGRES_SERVER", "postgres")
	config.ConnConfig.Port = uint16(port)
	config.ConnConfig.Database = GetenvOr("POSTGRES_DB", "kyoo")
	config.ConnConfig.User = GetenvOr("POSTGRES_USER", "kyoo")
	config.ConnConfig.Password = GetenvOr("POSTGRES_PASSWORD", "password")
	config.ConnConfig.TLSConfig = nil
	config.ConnConfig.RuntimeParams = map[string]string{
		"application_name": "keibi",
	}
	schema := GetenvOr("POSTGRES_SCHEMA", "keibi")
	if schema != "disabled" {
		config.ConnConfig.RuntimeParams["search_path"] = schema
	}

	db, err := pgxpool.NewWithConfig(ctx, config)
	if err != nil {
		fmt.Printf("Could not connect to database, check your env variables!")
		return nil, err
	}

	if schema != "disabled" {
		_, err = db.Exec(ctx, fmt.Sprintf("create schema if not exists %s", schema))
		if err != nil {
			return nil, err
		}
	}

	fmt.Println("Migrating database")
	dbi := stdlib.OpenDBFromPool(db)
	defer dbi.Close()

	driver, err := pgxd.WithInstance(dbi, &pgxd.Config{})
	if err != nil {
		return nil, err
	}
	m, err := migrate.NewWithDatabaseInstance("file://sql/migrations", "postgres", driver)
	if err != nil {
		return nil, err
	}
	m.Up()
	fmt.Println("Migrating finished")

	return db, nil
}

type Handler struct {
	db     *dbc.Queries
	config *Configuration
}

// @title Keibi - Kyoo's auth
// @version 1.0
// @description Auth system made for kyoo.

// @contact.name Repository
// @contact.url https://github.com/zoriya/kyoo

// @license.name  GPL-3.0
// @license.url https://www.gnu.org/licenses/gpl-3.0.en.html

// @host kyoo.zoriya.dev
// @BasePath /auth

// @securityDefinitions.apiKey Token
// @in header
// @name Authorization

// @securityDefinitions.apiKey Jwt
// @in header
// @name Authorization
func main() {
	e := echo.New()
	e.Use(middleware.Logger())
	e.Validator = &Validator{validator: validator.New(validator.WithRequiredStructEnabled())}
	e.HTTPErrorHandler = ErrorHandler

	db, err := OpenDatabase()
	if err != nil {
		e.Logger.Fatal("Could not open databse: ", err)
		return
	}

	h := Handler{
		db: dbc.New(db),
	}
	conf, err := LoadConfiguration(h.db)
	if err != nil {
		e.Logger.Fatal("Could not load configuration: ", err)
		return
	}
	h.config = conf

	g := e.Group(conf.Prefix)
	r := e.Group(conf.Prefix)
	r.Use(echojwt.WithConfig(echojwt.Config{
		SigningMethod: "RS256",
		SigningKey:    h.config.JwtPublicKey,
	}))

	g.GET("/health", h.CheckHealth)

	r.GET("/users", h.ListUsers)
	r.GET("/users/:id", h.GetUser)
	r.GET("/users/me", h.GetMe)
	r.DELETE("/users/:id", h.DeleteUser)
	r.DELETE("/users/me", h.DeleteSelf)
	g.POST("/users", h.Register)

	g.POST("/sessions", h.Login)
	r.DELETE("/sessions", h.Logout)
	r.DELETE("/sessions/:id", h.Logout)

	g.GET("/jwt", h.CreateJwt)
	g.GET("/info", h.GetInfo)

	g.GET("/swagger/*", echoSwagger.WrapHandler)

	e.Logger.Fatal(e.Start(":4568"))
}
