package db

import (
	"errors"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"sync"

	"src.rybka.ca/pkg/util"
	"src.rybka.ca/pkg/websocket"
)

type Server struct {
	Root      string
	listeners map[string]map[string]chan []byte
	mu        sync.Mutex
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		b, err := s.Read(r.URL.Path)
		if err != nil {
			if errors.Is(err, os.ErrNotExist) {
				http.Error(w, "not found", http.StatusNotFound)
			} else {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}
			return
		}
		if websocket.IsWebsocketRequest(r) {
			ch, c, err := s.Listen(r.URL.Path)
			conn, err := websocket.Upgrade(w, websocket.GetKey(r))
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			go func() {
				websocket.Read(conn)
				c()
			}()
			for b := range ch {
				websocket.Write(conn, b)
			}
		} else {
			w.Write(b)
		}
	case "PUT":
		b, err := io.ReadAll(r.Body)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		err = s.Write(r.URL.Path, b)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func (s *Server) Read(path string) ([]byte, error) {
	return os.ReadFile(filepath.Join(s.Root, path))
}

func (s *Server) Write(path string, b []byte) error {
	s.mu.Lock()
	p := filepath.Join(s.Root, path)
	err := os.MkdirAll(filepath.Dir(p), os.ModePerm)
	if err != nil {
		return err
	}
	err = os.WriteFile(p, b, os.ModePerm)
	if err != nil {
		return err
	}
	for _, l := range s.listeners[path] {
		l <- b
	}
	s.mu.Unlock()
	return nil
}

func (s *Server) Listen(path string) (<-chan []byte, func(), error) {
	b, err := s.Read(path)
	if err != nil {
		return nil, nil, err
	}
	ch, chanID := s.createListener(path)
	go func() {
		ch <- b
	}()
	return ch, func() {
		s.mu.Lock()
		close(ch)
		delete(s.listeners[path], chanID)
		s.mu.Unlock()
	}, nil
}

func (s *Server) createListener(path string) (chan []byte, string) {
	s.mu.Lock()
	ch := make(chan []byte)
	if s.listeners == nil {
		s.listeners = map[string]map[string]chan []byte{}
	}
	if s.listeners[path] == nil {
		s.listeners[path] = map[string]chan []byte{}
	}
	id := util.RandomString(16, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQURSTUVWXYZ1234567890")
	for {
		if _, ok := s.listeners[path][id]; !ok {
			break
		}
		id = util.RandomString(16, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQURSTUVWXYZ1234567890")
	}
	s.listeners[path][id] = ch
	s.mu.Unlock()
	return ch, id
}
