package server

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

	"git.faercol.me/faercol/polyculeconnect/polyculeconnect/config"
	"git.faercol.me/faercol/polyculeconnect/polyculeconnect/controller/auth"
	"git.faercol.me/faercol/polyculeconnect/polyculeconnect/controller/ui"
	"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/middlewares"
	"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/storage"
	"github.com/zitadel/oidc/v3/pkg/op"
	"go.uber.org/zap"
)

type Server struct {
	ctx         context.Context
	cancel      context.CancelFunc
	httpSrv     *http.Server
	listener    net.Listener
	serverMode  config.ListeningMode
	address     string
	handler     *http.ServeMux
	controllers map[string]http.Handler
	l           *zap.SugaredLogger
}

func newUnixListener(sockPath string) (net.Listener, error) {
	if err := os.Remove(sockPath); err != nil && !errors.Is(err, os.ErrNotExist) {
		return nil, fmt.Errorf("failed to cleanup previously existing socket: %w", err)
	}

	sock, err := net.Listen("unix", sockPath)
	if err != nil {
		return nil, fmt.Errorf("failed to create unix socket: %w", err)
	}
	if err := os.Chmod(sockPath, 0o777); err != nil {
		return nil, fmt.Errorf("failed to set permissions to unix socket: %w", err)
	}
	return sock, nil
}

func New(appConf *config.AppConfig, oidcHandler *op.Provider, st *storage.Storage, logger *zap.SugaredLogger) (*Server, error) {
	var listener net.Listener
	var addr string
	var err error
	switch appConf.ServerMode {
	case config.ModeNet:
		addr = fmt.Sprintf("%s:%d", appConf.Host, appConf.Port)
		listener, err = net.Listen("tcp", addr)
		if err != nil {
			return nil, fmt.Errorf("failed to init server in net mode: %w", err)
		}
	case config.ModeUnix:
		addr = appConf.SockPath
		listener, err = newUnixListener(appConf.SockPath)
		if err != nil {
			return nil, fmt.Errorf("failed to init server in unix mode: %w", err)
		}
	default:
		panic(fmt.Errorf("unexpected listening mode %v", appConf.ServerMode))
	}

	controllers := map[string]http.Handler{
		auth.AuthCallbackRoute: middlewares.WithLogger(auth.NewAuthCallbackController(logger, st), logger),
		auth.AuthRedirectRoute: middlewares.WithLogger(auth.NewAuthRedirectController(logger, st), logger),
		ui.StaticRoute:         middlewares.WithLogger(ui.NewStaticController(appConf.StaticDir), logger),
		"/":                    middlewares.WithLogger(ui.NewIndexController(logger, oidcHandler, appConf.StaticDir), logger),
	}

	m := http.NewServeMux()

	return &Server{
		handler: m,
		httpSrv: &http.Server{
			Handler: m,
		},
		listener:    listener,
		l:           logger,
		serverMode:  appConf.ServerMode,
		address:     addr,
		controllers: controllers,
		ctx:         context.TODO(),
	}, nil
}

func (s *Server) initMux() {
	for r, c := range s.controllers {
		s.handler.Handle(r, c)
	}
}

func (s *Server) Run(ctx context.Context) {
	s.ctx, s.cancel = context.WithCancel(ctx)
	s.initMux()
	switch s.serverMode {
	case config.ModeNet:
		s.l.Infof("Server listening on host %q", s.address)
	case config.ModeUnix:
		s.l.Infof("Server listening on unix socket %q", s.address)
	default:
	}
	if err := s.httpSrv.Serve(s.listener); err != nil {
		s.l.Errorf("failed to serve HTTP server: %s", err.Error())
	}
	s.cancel()
}

func (s *Server) Done() <-chan struct{} {
	return s.ctx.Done()
}
