package bootstrap

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

	"go.uber.org/zap"
)

// ArtifactServer serves node bootstrap scripts and node binaries from the
// configured artifact directory. It is intentionally static: iop-edge prepares
// the directory, then serve exposes it without mutating the artifacts.
type ArtifactServer struct {
	listen string
	dir    string
	logger *zap.Logger
	server *http.Server
}

func NewArtifactServer(listen, dir string, logger *zap.Logger) *ArtifactServer {
	return &ArtifactServer{listen: listen, dir: dir, logger: logger}
}

func (s *ArtifactServer) Start(ctx context.Context) error {
	if s == nil || s.listen == "" || s.dir == "" {
		return nil
	}
	if _, err := os.Stat(s.dir); err != nil {
		if errors.Is(err, os.ErrNotExist) {
			s.logger.Warn("bootstrap artifact directory does not exist", zap.String("dir", s.dir))
		} else {
			return fmt.Errorf("stat bootstrap artifact dir: %w", err)
		}
	}

	mux := http.NewServeMux()
	mux.Handle("/", http.FileServer(http.Dir(s.dir)))
	s.server = &http.Server{
		Addr:              s.listen,
		Handler:           mux,
		ReadHeaderTimeout: 5 * time.Second,
	}
	ln, err := net.Listen("tcp", s.listen)
	if err != nil {
		return fmt.Errorf("bootstrap artifact server listen %s: %w", s.listen, err)
	}
	go func() {
		<-ctx.Done()
		shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		_ = s.server.Shutdown(shutdownCtx)
	}()
	go func() {
		s.logger.Info("bootstrap artifact server listening", zap.String("addr", s.listen), zap.String("dir", s.dir))
		if err := s.server.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) {
			s.logger.Warn("bootstrap artifact server exited", zap.Error(err))
		}
	}()
	return nil
}

func (s *ArtifactServer) Stop(ctx context.Context) error {
	if s == nil || s.server == nil {
		return nil
	}
	return s.server.Shutdown(ctx)
}
