package server

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log/slog"
	"net/http"
	"os"

	"git.t-juice.club/microfilm/auth"
	"git.t-juice.club/microfilm/auth/authmw"
	"git.t-juice.club/microfilm/users"
	"git.t-juice.club/microfilm/users/store"
	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
	"github.com/nats-io/nats.go"
)

type UserServer struct {
	http.Server
	store  store.UserStore
	config *Config
	nats   *nats.Conn
	Logger *slog.Logger
}

func NewServer(config *Config) (*UserServer, error) {
	r := chi.NewRouter()
	srv := &UserServer{}
	srv.config = config

	srv.Logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelDebug,
	}))

	r.Use(srv.MiddlewareLogging)

	verifyAdmin := authmw.VerifyToken("http://mf-auth:8082", []string{auth.RoleAdmin})

	r.Get("/info", InfoHandler)
	r.With(verifyAdmin).Post("/", srv.CreateUserHandler)
	r.Get("/{identifier}", srv.GetUserHandler)
	r.Post("/{identifier}/password", srv.SetPasswordHandler)
	r.Post("/{identifier}/verify", srv.VerifyHandler)

	srv.Addr = config.ListenAddr

	srv.Handler = r
	srv.store = store.NewMemoryStore()

	// Add initial admin-user
	u := users.User{
		ID:       uuid.Must(uuid.NewRandom()).String(),
		Username: "admin",
		Role:     "admin",
	}
	password := uuid.Must(uuid.NewRandom()).String()
	_ = u.SetPassword(password)
	_ = srv.store.AddUser(u)

	srv.Logger.Warn("Initial admin-user created.", "username", u.Username, "password", password)

	conn, err := nats.Connect(config.NATSAddr)
	if err != nil {
		return nil, err
	}

	srv.nats = conn

	return srv, nil
}

func InfoHandler(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)

	data := &users.InfoResponse{
		Version: users.Version,
	}

	_ = enc.Encode(data)
}

func WriteError(w http.ResponseWriter, response users.ErrorResponse) {
	w.WriteHeader(response.Status)

	encoder := json.NewEncoder(w)
	_ = encoder.Encode(&response)
}

func (s *UserServer) CreateUserHandler(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	defer r.Body.Close()

	var request users.CreateUserRequest

	if err := decoder.Decode(&request); err != nil {
		WriteError(w, users.ErrorResponse{
			Status:  http.StatusBadRequest,
			Message: fmt.Sprintf("Error parsing request: %s", err),
		})
		return
	}

	id := uuid.Must(uuid.NewRandom())
	u := users.User{
		ID:       id.String(),
		Username: request.Username,
	}

	if err := u.SetPassword(request.Password); err != nil {
		WriteError(w, users.ErrorResponse{
			Status:  http.StatusInternalServerError,
			Message: fmt.Sprintf("Error setting user password: %s", err),
		})
		return
	}

	if err := s.store.AddUser(u); err != nil {
		s.Logger.Warn("Error storing user", "error", err)
		WriteError(w, users.ErrorResponse{
			Status:  http.StatusInternalServerError,
			Message: fmt.Sprintf("Error storing user: %s", err),
		})
		return
	}

	// Message
	sub := fmt.Sprintf("%s.%s", s.config.NATSSubject, "create")

	var buf bytes.Buffer
	msg := &users.MsgUserCreate{
		Message: "User created.",
		User:    u,
	}
	encoder := json.NewEncoder(&buf)
	_ = encoder.Encode(&msg)
	if err := s.nats.Publish(sub, buf.Bytes()); err != nil {
		s.Logger.Warn("Error publishing message", "error", err)
	}

	s.Logger.Info("User created.", "username", u.Username, "id", u.ID)

	response := &users.CreateUserResponse{
		Message: "User created.",
		User:    u,
	}

	encoder = json.NewEncoder(w)
	_ = encoder.Encode(&response)
}

func (s *UserServer) GetUserHandler(w http.ResponseWriter, r *http.Request) {
	identifier := chi.URLParam(r, "identifier")
	u, err := s.store.GetUser(identifier)
	if err != nil {
		switch err {
		case store.ErrNoSuchUser:
			WriteError(w, users.ErrorResponse{
				Message: fmt.Sprintf("No such user: %s", identifier),
				Status:  http.StatusNotFound,
			})
			return
		}
		WriteError(w, users.ErrorResponse{
			Message: fmt.Sprintf("Unable to get user: %s", err),
			Status:  http.StatusInternalServerError,
		})
		return
	}

	encoder := json.NewEncoder(w)
	_ = encoder.Encode(&u)
}

func (s *UserServer) SetPasswordHandler(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	defer r.Body.Close()

	var request users.SetPasswordRequest

	if err := decoder.Decode(&request); err != nil {
		WriteError(w, users.ErrorResponse{
			Status:  http.StatusBadRequest,
			Message: fmt.Sprintf("Error parsing request: %s", err),
		})
		return
	}

	id := chi.URLParam(r, "identifier")
	if id == "" {
		WriteError(w, users.ErrorResponse{
			Status:  http.StatusBadRequest,
			Message: fmt.Sprintf("Invalid user ID: %s", id),
		})
		return
	}

	u, err := s.store.GetUser(id)
	if err != nil {
		msg := fmt.Sprintf("Server error: %s", err)
		status := http.StatusInternalServerError

		switch err {
		case store.ErrNoSuchUser:
			msg = "No user with that ID"
			status = http.StatusNotFound
		}

		WriteError(w, users.ErrorResponse{
			Status:  status,
			Message: msg,
		})
		return
	}

	if err := u.SetPassword(request.NewPassword); err != nil {
		WriteError(w, users.ErrorResponse{
			Status:  http.StatusBadRequest,
			Message: fmt.Sprintf("Unable to set password: %s", id),
		})
		return
	}

	if err := s.store.UpdateUser(u); err != nil {
		s.Logger.Warn("Unable to update user.", "id", u.ID, "error", err)
		WriteError(w, users.ErrorResponse{
			Status:  http.StatusInternalServerError,
			Message: fmt.Sprintf("Unable to set password: %s", id),
		})
		return
	}

	sub := fmt.Sprintf("%s.%s", s.config.NATSSubject, "update")
	var buf bytes.Buffer
	encoder := json.NewEncoder(&buf)
	_ = encoder.Encode(&users.MsgUserUpdate{Message: "Password updated", ID: u.ID})

	if err := s.nats.Publish(sub, buf.Bytes()); err != nil {
		s.Logger.Warn("Error publishing message", "error", err)
	}
	s.Logger.Info("User password updated.", "id", u.ID)
}

func (s *UserServer) VerifyHandler(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	defer r.Body.Close()

	var request users.VerifyRequest

	if err := decoder.Decode(&request); err != nil {
		WriteError(w, users.ErrorResponse{
			Status:  http.StatusBadRequest,
			Message: fmt.Sprintf("Error parsing request: %s", err),
		})
		return
	}

	id := chi.URLParam(r, "identifier")
	if id == "" {
		WriteError(w, users.ErrorResponse{
			Status:  http.StatusBadRequest,
			Message: fmt.Sprintf("Invalid user ID: %s", id),
		})
		return
	}

	u, err := s.store.GetUser(id)
	if err != nil {
		msg := fmt.Sprintf("Server error: %s", err)
		status := http.StatusInternalServerError

		switch err {
		case store.ErrNoSuchUser:
			msg = "No user with that ID"
			status = http.StatusNotFound
		}

		WriteError(w, users.ErrorResponse{
			Status:  status,
			Message: msg,
		})
		return
	}

	err = u.ComparePassword(request.Password)
	if err != nil {
		WriteError(w, users.ErrorResponse{
			Status:  http.StatusUnauthorized,
			Message: "Password verification failed.",
		})
		return
	}
	w.WriteHeader(http.StatusOK)
}