package cow

import (
	"context"
	"errors"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/openeuler/Conch/internal/memsnap"
	"golang.org/x/sys/unix"
)

type attachment struct {
	token     string
	sandboxID string
	pinned    *memsnap.PinnedManifest
	memfd     *os.File

	uffdSocketPath  string
	uffdListener    *net.UnixListener
	uffd            *os.File
	uffdRanges      []uffdRange
	handoffPrepared bool
	handoffDone     chan struct{}
	handoffErr      error
	workerStop      chan struct{}
	workerDone      chan struct{}
	closeOnce       sync.Once
	closeErr        error
}

type Server struct {
	socketPath   string
	capabilities Capabilities

	mu                    sync.Mutex
	attachments           map[string]*attachment
	listener              *net.UnixListener
	closed                bool
	connections           sync.WaitGroup
	activeConnections     map[*net.UnixConn]struct{}
	uffdOps               uffdOperations
	uffdSequence          uint64
	controlSocketIdentity unixSocketIdentity
	controlSocketOwned    bool
	ready                 chan struct{}
	readyOnce             sync.Once
}

type unixSocketIdentity struct {
	device uint64
	inode  uint64
}

func NewServer(socketPath string) *Server {
	if socketPath == "" {
		socketPath = DefaultSocketPath
	}
	return newServer(socketPath, ProbeCapabilities())
}

func newServer(socketPath string, capabilities Capabilities) *Server {
	return &Server{
		socketPath:        socketPath,
		capabilities:      capabilities,
		attachments:       make(map[string]*attachment),
		activeConnections: make(map[*net.UnixConn]struct{}),
		uffdOps:           productionUFFDOperations(),
		ready:             make(chan struct{}),
	}
}

func (server *Server) Ready() <-chan struct{} {
	return server.ready
}

func baseResponse(request Request) Response {
	return Response{ProtocolVersion: ProtocolVersion, RequestID: request.RequestID}
}

func (server *Server) Serve(ctx context.Context) error {
	if server.socketPath == "" {
		return fmt.Errorf("socket path is required")
	}
	if err := os.MkdirAll(filepath.Dir(server.socketPath), 0o755); err != nil {
		return fmt.Errorf("create cow socket directory: %w", err)
	}
	if err := prepareControlSocketPath(server.socketPath); err != nil {
		return err
	}
	listener, err := net.ListenUnix("unix", &net.UnixAddr{Name: server.socketPath, Net: "unix"})
	if err != nil {
		return fmt.Errorf("listen cow socket: %w", err)
	}
	listener.SetUnlinkOnClose(false)
	identity, err := controlSocketIdentity(server.socketPath)
	if err != nil {
		_ = listener.Close()
		return fmt.Errorf("identify cow socket: %w", err)
	}
	if err := os.Chmod(server.socketPath, 0o600); err != nil {
		_ = listener.Close()
		_ = removeControlSocketIfOwned(server.socketPath, identity)
		return fmt.Errorf("chmod cow socket: %w", err)
	}
	server.mu.Lock()
	if server.closed {
		server.mu.Unlock()
		_ = listener.Close()
		_ = removeControlSocketIfOwned(server.socketPath, identity)
		return fmt.Errorf("server is closed")
	}
	server.listener = listener
	server.controlSocketIdentity = identity
	server.controlSocketOwned = true
	server.mu.Unlock()
	server.readyOnce.Do(func() { close(server.ready) })

	stop := make(chan struct{})
	go func() {
		select {
		case <-ctx.Done():
			_ = listener.Close()
		case <-stop:
		}
	}()
	defer close(stop)
	defer server.removeOwnedControlSocket()
	for {
		conn, err := listener.AcceptUnix()
		if err != nil {
			if ctx.Err() != nil || server.isClosed() || errors.Is(err, net.ErrClosed) {
				return nil
			}
			return fmt.Errorf("accept cow connection: %w", err)
		}
		server.mu.Lock()
		if server.closed {
			server.mu.Unlock()
			_ = conn.Close()
			continue
		}
		server.connections.Add(1)
		server.activeConnections[conn] = struct{}{}
		server.mu.Unlock()
		go func(conn *net.UnixConn) {
			defer server.connections.Done()
			defer func() {
				server.mu.Lock()
				delete(server.activeConnections, conn)
				server.mu.Unlock()
			}()
			server.serveConnection(conn)
		}(conn)
	}
}

func (server *Server) serveConnection(conn *net.UnixConn) {
	defer conn.Close()
	_ = conn.SetDeadline(time.Now().Add(requestTimeout))
	var request Request
	received, err := readFrame(conn, &request)
	if err != nil {
		return
	}
	response := baseResponse(request)
	if err := validateRequest(request, received); err != nil {
		closeFDs(received)
		response.Error = err.Error()
		_ = writeFrame(conn, response, nil)
		return
	}
	if request.Type == RequestWaitAttachmentReady {
		_ = conn.SetDeadline(time.Now().Add(server.uffdOps.acceptTimeout + waitReadyTimeoutMargin))
	}
	var sendFDs []int
	switch request.Type {
	case RequestCapabilities:
		response.OK = true
		capabilities := server.capabilities
		capabilities.MissingFeatures = append([]string(nil), capabilities.MissingFeatures...)
		response.Capabilities = &capabilities
	case RequestAttach:
		response, sendFDs = server.handleAttach(request, response)
	case RequestWaitAttachmentReady:
		response = server.handleWaitAttachmentReady(request, response)
	case RequestDetach:
		response = server.handleDetach(request, response)
	}
	closeFDs(received)
	defer closeFDs(sendFDs)
	if err := writeFrame(conn, response, sendFDs); err != nil && request.Type == RequestAttach && response.OK {
		server.releaseAttachment(response.Token)
	}
}

func (server *Server) handleAttach(request Request, response Response) (Response, []int) {
	if request.SandboxID == "" || request.MemorySnapshotRoot == "" {
		response.Error = "sandbox ID and memory snapshot root are required"
		return response, nil
	}
	if server.capabilities.IncrementalMemory != CapabilitySupported {
		response.Error = fmt.Sprintf("incremental memory is %s", server.capabilities.IncrementalMemory)
		return response, nil
	}
	pinned, err := memsnap.LoadAndPin(request.MemorySnapshotRoot)
	if err != nil {
		response.Error = fmt.Sprintf("load and pin manifest: %v", err)
		return response, nil
	}
	token := uuid.NewString()
	fd, err := unix.MemfdCreate("conch-memory-"+token, unix.MFD_CLOEXEC|unix.MFD_ALLOW_SEALING)
	if err != nil {
		_ = pinned.Close()
		response.Error = fmt.Sprintf("create memory memfd: %v", err)
		return response, nil
	}
	owner := os.NewFile(uintptr(fd), "conch-memory-owner")
	if owner == nil {
		_ = unix.Close(fd)
		_ = pinned.Close()
		response.Error = "wrap memory memfd"
		return response, nil
	}
	cleanup := func() {
		_ = owner.Close()
		_ = pinned.Close()
	}
	if pinned.Manifest.MemorySize > uint64(^uint64(0)>>1) {
		cleanup()
		response.Error = "memory size exceeds supported file size"
		return response, nil
	}
	if err := unix.Ftruncate(fd, int64(pinned.Manifest.MemorySize)); err != nil {
		cleanup()
		response.Error = fmt.Sprintf("size memory memfd: %v", err)
		return response, nil
	}
	if _, err := unix.FcntlInt(uintptr(fd), unix.F_ADD_SEALS, unix.F_SEAL_GROW|unix.F_SEAL_SHRINK); err != nil {
		cleanup()
		response.Error = fmt.Sprintf("seal memory memfd: %v", err)
		return response, nil
	}
	duplicate, err := unix.FcntlInt(uintptr(fd), unix.F_DUPFD_CLOEXEC, 0)
	if err != nil {
		cleanup()
		response.Error = fmt.Sprintf("duplicate memory memfd: %v", err)
		return response, nil
	}
	item := &attachment{token: token, sandboxID: request.SandboxID, pinned: pinned, memfd: owner}
	server.mu.Lock()
	if server.closed {
		server.mu.Unlock()
		_ = unix.Close(duplicate)
		cleanup()
		response.Error = "server is closed"
		return response, nil
	}
	server.attachments[token] = item
	server.mu.Unlock()
	prepared := server.prepareUFFDHandoff(item, response)
	if !prepared.OK {
		_ = unix.Close(duplicate)
		server.releaseAttachment(token)
		return prepared, nil
	}
	prepared.Token = token
	prepared.MemorySize = pinned.Manifest.MemorySize
	prepared.BlockSize = pinned.Manifest.BlockSize
	return prepared, []int{duplicate}
}

func (server *Server) handleDetach(request Request, response Response) Response {
	server.releaseAttachment(request.Token)
	response.OK = true
	return response
}

func (server *Server) releaseAttachment(token string) bool {
	server.mu.Lock()
	item := server.attachments[token]
	if item != nil {
		delete(server.attachments, token)
	}
	server.mu.Unlock()
	if item == nil {
		return false
	}
	_ = server.closeAttachment(item)
	return true
}

func (server *Server) handleWaitAttachmentReady(request Request, response Response) Response {
	server.mu.Lock()
	if server.closed {
		server.mu.Unlock()
		response.Error = "server is closed"
		return response
	}
	item := server.attachments[request.Token]
	if item == nil || !item.handoffPrepared || item.sandboxID != request.SandboxID {
		server.mu.Unlock()
		response.Error = "UFFD handoff is not prepared for sandbox"
		return response
	}
	handoffDone := item.handoffDone
	server.mu.Unlock()
	if handoffDone != nil {
		timer := time.NewTimer(server.uffdOps.acceptTimeout + time.Second)
		defer timer.Stop()
		select {
		case <-handoffDone:
		case <-timer.C:
			response.Error = "timed out waiting for UFFD handoff"
			return response
		}
	}
	server.mu.Lock()
	defer server.mu.Unlock()
	if server.closed || server.attachments[request.Token] != item {
		response.Error = "attachment changed while waiting for UFFD"
		return response
	}
	if item.handoffErr != nil {
		response.Error = fmt.Sprintf("UFFD handoff failed: %v", item.handoffErr)
		return response
	}
	if item.uffd == nil {
		response.Error = "UFFD handoff did not provide a fault descriptor"
		return response
	}
	response.OK = true
	return response
}

func (server *Server) closeAttachment(item *attachment) error {
	if item == nil {
		return nil
	}
	item.closeOnce.Do(func() {
		var result error
		server.mu.Lock()
		listener := item.uffdListener
		handoffDone := item.handoffDone
		socketPath := item.uffdSocketPath
		server.mu.Unlock()
		if listener != nil {
			_ = listener.Close()
		}
		if socketPath != "" {
			if err := os.Remove(socketPath); err != nil && !errors.Is(err, os.ErrNotExist) {
				result = errors.Join(result, err)
			}
		}
		if handoffDone != nil {
			<-handoffDone
		}
		server.mu.Lock()
		workerStop := item.workerStop
		workerDone := item.workerDone
		uffd := item.uffd
		server.mu.Unlock()
		if workerStop != nil {
			close(workerStop)
		}
		if workerDone != nil {
			<-workerDone
		} else if uffd != nil {
			result = errors.Join(result, uffd.Close())
		}
		if item.memfd != nil {
			result = errors.Join(result, item.memfd.Close())
		}
		if item.pinned != nil {
			result = errors.Join(result, item.pinned.Close())
		}
		item.closeErr = result
	})
	return item.closeErr
}

func (server *Server) Close() error {
	server.mu.Lock()
	firstClose := !server.closed
	server.closed = true
	listener := server.listener
	var handoffListeners []*net.UnixListener
	var activeConnections []*net.UnixConn
	for _, item := range server.attachments {
		if item.uffdListener != nil {
			handoffListeners = append(handoffListeners, item.uffdListener)
		}
	}
	for conn := range server.activeConnections {
		activeConnections = append(activeConnections, conn)
	}
	server.mu.Unlock()
	if firstClose && listener != nil {
		_ = listener.Close()
	}
	for _, handoffListener := range handoffListeners {
		_ = handoffListener.Close()
	}
	for _, conn := range activeConnections {
		_ = conn.Close()
	}
	server.connections.Wait()
	server.mu.Lock()
	attachments := make(map[string]*attachment, len(server.attachments))
	for token, item := range server.attachments {
		attachments[token] = item
	}
	server.mu.Unlock()
	var result error
	for token, item := range attachments {
		result = errors.Join(result, server.closeAttachment(item))
		server.mu.Lock()
		if server.attachments[token] == item {
			delete(server.attachments, token)
		}
		server.mu.Unlock()
	}
	return errors.Join(result, server.removeOwnedControlSocket())
}

func controlSocketIdentity(path string) (unixSocketIdentity, error) {
	var stat unix.Stat_t
	if err := unix.Lstat(path, &stat); err != nil {
		return unixSocketIdentity{}, err
	}
	if stat.Mode&unix.S_IFMT != unix.S_IFSOCK {
		return unixSocketIdentity{}, fmt.Errorf("path is not a Unix socket")
	}
	return unixSocketIdentity{device: uint64(stat.Dev), inode: stat.Ino}, nil
}

func prepareControlSocketPath(path string) error {
	identity, err := controlSocketIdentity(path)
	if errors.Is(err, unix.ENOENT) {
		return nil
	}
	if err != nil {
		return fmt.Errorf("control socket path %q already exists", path)
	}
	connection, dialErr := net.DialTimeout("unix", path, 100*time.Millisecond)
	if dialErr == nil {
		_ = connection.Close()
		return fmt.Errorf("control socket path %q already exists", path)
	}
	if !errors.Is(dialErr, unix.ECONNREFUSED) && !errors.Is(dialErr, os.ErrNotExist) {
		return fmt.Errorf("control socket path %q already exists: %w", path, dialErr)
	}
	return removeControlSocketIfOwned(path, identity)
}

func removeControlSocketIfOwned(path string, expected unixSocketIdentity) error {
	actual, err := controlSocketIdentity(path)
	if errors.Is(err, unix.ENOENT) || err != nil || actual != expected {
		return nil
	}
	return unix.Unlink(path)
}

func (server *Server) removeOwnedControlSocket() error {
	server.mu.Lock()
	if !server.controlSocketOwned {
		server.mu.Unlock()
		return nil
	}
	server.controlSocketOwned = false
	identity := server.controlSocketIdentity
	path := server.socketPath
	server.mu.Unlock()
	return removeControlSocketIfOwned(path, identity)
}

func (server *Server) isClosed() bool {
	server.mu.Lock()
	defer server.mu.Unlock()
	return server.closed
}