package main

import (
	"context"
	"errors"
	"expvar"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/FernandoVideira/LK_API_Temp/docs" // This is required to autogenerate swagger docs
	"github.com/FernandoVideira/LK_API_Temp/internal/auth"
	"github.com/FernandoVideira/LK_API_Temp/internal/mailer"
	"github.com/FernandoVideira/LK_API_Temp/internal/ratelimiter"
	"github.com/FernandoVideira/LK_API_Temp/internal/store"
	"github.com/FernandoVideira/LK_API_Temp/internal/store/cache"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/go-chi/cors"
	httpSwagger "github.com/swaggo/http-swagger/v2" // http-swagger middleware

	"go.uber.org/zap"
)

type api struct {
	config        config
	store         store.Storage
	cacheStore    cache.Storage
	logger        *zap.SugaredLogger
	mailer        mailer.Client
	authenticator auth.Authenticator
	rateLimiter   ratelimiter.Limiter
}

type config struct {
	addr        string
	db          dbConfig
	env         string
	apiURL      string
	mail        mailConfig
	frontEndURL string
	auth        authConfig
	redisCfg    redisConfig
	rateLimiter ratelimiter.Config
}

type mailConfig struct {
	exp       time.Duration
	fromEmail string
	sendGrid  sendGridConfig
}

type authConfig struct {
	basic basicConfig
	token tokenConfig
}

type basicConfig struct {
	username string
	password string
}

type tokenConfig struct {
	secret string
	exp    time.Duration
	iss    string
	aud    string
}

type redisConfig struct {
	addr    string
	pw      string
	db      int
	enabled bool
}

// TODO: Switch to previously made SMTP mailer
type sendGridConfig struct {
	apiKey string
}

type dbConfig struct {
	addr         string
	maxOpenConns int
	maxIdleConns int
	maxIdleTime  time.Duration
}

func (api *api) mount() http.Handler {
	r := chi.NewRouter()
	// *CORS
	r.Use(middleware.RequestID)
	r.Use(middleware.RealIP)
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)

	r.Use(cors.Handler(cors.Options{
		AllowedOrigins: []string{"https://*", "http://*"}, //* Use this for development
		//AllowedOrigins:   []string{env.GetList("ALLOWED_ORIGINS", []string{"https://lk-api-temp.onrender.com"})}, //* Use this for production
		AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
		ExposedHeaders:   []string{"Link"},
		AllowCredentials: false,
		MaxAge:           300,
	}))
	r.Use(api.RateLimiterMiddleware)

	r.Use(middleware.Timeout(60 * time.Second))

	r.Route("/v1", func(r chi.Router) {

		//r.With(api.BasicAuthMiddleware()).Get("/health", api.healthCheckHandler)
		r.Get("/health", api.healthCheckHandler)
		r.With(api.BasicAuthMiddleware()).Get("/debug/vars", expvar.Handler().ServeHTTP)

		docsURL := fmt.Sprintf("%s/swagger/doc.json", api.config.addr)
		r.Get("/swagger/*", httpSwagger.Handler(httpSwagger.URL(docsURL)))

		r.Route("/posts", func(r chi.Router) {
			r.Use(api.AuthTokenMiddleware)
			r.Post("/", api.createPostHandler)

			r.Route("/{id}", func(r chi.Router) {
				r.Use(api.postsContextMiddleware)

				r.Get("/", api.getPostHandler)
				r.Patch("/", api.checkPostOwnership("moderator", api.updatePostHandler))
				r.Delete("/", api.checkPostOwnership("admin", api.deletePostHandler))
				r.Post("/comments", api.createCommentHandler)
			})
		})

		r.Route("/users", func(r chi.Router) {
			r.Put("/activate/{token}", api.activateUserHandler)

			r.Route("/{id}", func(r chi.Router) {
				r.Use(api.AuthTokenMiddleware)

				r.Get("/", api.getUserHandler)
				r.Patch("/", api.checkProfileOwnership(api.updateUserHandler))
				r.Patch("/password", api.checkProfileOwnership(api.updatePasswordHandler))
				r.Put(("/follow"), api.followUserHandler)
				r.Put(("/unfollow"), api.unfollowUserHandler)
			})
			r.Group(func(r chi.Router) {
				r.Use(api.AuthTokenMiddleware)
				r.Get("/feed", api.getUserFeedHandler)
			})
		})

		r.Route("/authentication", func(r chi.Router) {
			r.Post("/user", api.registerUserHandler)
			r.Post("/token", api.createTokenHandler)

		})
	})

	return r
}

func (api *api) run(mux http.Handler) error {

	//Docs
	docs.SwaggerInfo.Version = "0.0.1"
	docs.SwaggerInfo.Host = api.config.apiURL
	docs.SwaggerInfo.BasePath = "/v1"

	srv := http.Server{
		Addr:         api.config.addr,
		Handler:      mux,
		WriteTimeout: 30 * time.Second,
		ReadTimeout:  10 * time.Second,
		IdleTimeout:  time.Minute,
	}

	shutdown := make(chan error)

	go func() {
		quit := make(chan os.Signal, 1)
		signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
		s := <-quit

		ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
		defer cancel()

		api.logger.Infow("shutting down server", "signal", s.String())

		shutdown <- srv.Shutdown(ctx)

	}()

	api.logger.Infow("server has started", "addr", api.config.addr, "env", api.config.env)

	err := srv.ListenAndServe()
	if !errors.Is(err, http.ErrServerClosed) {
		return err
	}

	err = <-shutdown
	if err != nil {
		return err
	}

	api.logger.Infow("server has been shutdown")

	return nil
}
