/*
 * Copyright 2022 CloudWeGo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Package gonet contains server and client implementation for go net.
package gonet

import (
	"context"
	"errors"
	"net"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"time"

	"github.com/cloudwego/kitex/pkg/remote/trans"

	"github.com/cloudwego/kitex/pkg/klog"
	"github.com/cloudwego/kitex/pkg/remote"
	"github.com/cloudwego/kitex/pkg/rpcinfo"
	"github.com/cloudwego/kitex/pkg/utils"
)

const defaultShutdownTicker = 100 * time.Millisecond

// NewTransServerFactory creates a default go net transport server factory.
func NewTransServerFactory() remote.TransServerFactory {
	return &gonetTransServerFactory{}
}

type gonetTransServerFactory struct{}

// NewTransServer implements the remote.TransServerFactory interface.
func (f *gonetTransServerFactory) NewTransServer(opt *remote.ServerOption, transHdlr remote.ServerTransHandler) remote.TransServer {
	return &transServer{
		opt:       opt,
		transHdlr: transHdlr,
		lncfg:     trans.NewListenConfig(opt),
	}
}

type transServer struct {
	opt       *remote.ServerOption
	transHdlr remote.ServerTransHandler
	ln        net.Listener
	lncfg     net.ListenConfig
	connCount utils.AtomicInt
	shutdown  uint32 // 1: shutdown
	sync.Mutex
}

var _ remote.TransServer = &transServer{}

// CreateListener implements the remote.TransServer interface.
// The network must be "tcp", "tcp4", "tcp6" or "unix".
func (ts *transServer) CreateListener(addr net.Addr) (ln net.Listener, err error) {
	ln, err = ts.lncfg.Listen(context.Background(), addr.Network(), addr.String())
	return ln, err
}

// BootstrapServer implements the remote.TransServer interface.
func (ts *transServer) BootstrapServer(ln net.Listener) error {
	if ln == nil {
		return errors.New("listener is nil in gonet transport server")
	}
	ts.Lock()
	ts.ln = ln
	ts.Unlock()

	for {
		conn, err := ln.Accept()
		if err != nil {
			if atomic.LoadUint32(&ts.shutdown) == 1 {
				// shutdown
				return nil
			}
			klog.Errorf("KITEX: BootstrapServer accept failed, err=%s", err.Error())
			return err
		}
		go ts.serveConn(context.Background(), conn)
	}
}

func (ts *transServer) serveConn(ctx context.Context, conn net.Conn) (err error) {
	defer transRecover(ctx, conn, "serveConn")

	ts.connCount.Inc()
	bc := newSvrConn(conn)
	defer func() {
		if err != nil {
			ts.onError(ctx, err, bc)
		}
		ts.connCount.Dec()
		bc.Close()
	}()

	ctx, err = ts.transHdlr.OnActive(ctx, bc)
	if err != nil {
		klog.CtxErrorf(ctx, "KITEX: OnActive error=%s", err)
		return err
	}
	for {
		// block to wait for next request
		ts.refreshIdleDeadline(bc)
		_, err = bc.r.Peek(1)
		if err != nil {
			return err
		}
		// set the read deadline if ReadTimeout configured
		ts.refreshReadDeadline(rpcinfo.GetRPCInfo(ctx), bc)
		// FIXME: for gRPC transHandler, OnRead should execute only once.
		err = ts.transHdlr.OnRead(ctx, bc)
		if err != nil {
			return err
		}
	}
}

// Shutdown implements the remote.TransServer interface.
func (ts *transServer) Shutdown() (err error) {
	atomic.StoreUint32(&ts.shutdown, 1)

	ts.Lock()
	defer ts.Unlock()

	ctx, cancel := context.WithTimeout(context.Background(), ts.opt.ExitWaitTime)
	defer cancel()
	if g, ok := ts.transHdlr.(remote.GracefulShutdown); ok {
		if ts.ln != nil {
			// 1. stop listener
			_ = ts.ln.Close()

			// 2. signal all active connections to close gracefully
			_ = g.GracefulShutdown(ctx)
		}
	}

	shutdownTicker := time.NewTicker(defaultShutdownTicker)
	defer shutdownTicker.Stop()
	for {
		select {
		case <-shutdownTicker.C:
			// check active conn count
			if ts.connCount.Value() == 0 {
				return nil
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

// ConnCount implements the remote.TransServer interface.
func (ts *transServer) ConnCount() utils.AtomicInt {
	return utils.AtomicInt(ts.connCount.Value())
}

func (ts *transServer) onError(ctx context.Context, err error, conn net.Conn) {
	ts.transHdlr.OnError(ctx, err, conn)
}

func (ts *transServer) refreshIdleDeadline(conn net.Conn) {
	// set the default setting to 2 minutes to make sure it's greater
	// than the client idle timeout.
	idleTimeout := 2 * time.Minute
	if ts.opt.MaxConnectionIdleTime > idleTimeout {
		// use MaxConnectionIdleTime if larger than the old default timeout
		idleTimeout = ts.opt.MaxConnectionIdleTime
	}
	conn.SetReadDeadline(time.Now().Add(idleTimeout))
}

func (ts *transServer) refreshReadDeadline(ri rpcinfo.RPCInfo, conn net.Conn) {
	readTimeout := ri.Config().ReadWriteTimeout()
	if readTimeout > 0 {
		_ = conn.SetReadDeadline(time.Now().Add(readTimeout))
	} else {
		_ = conn.SetReadDeadline(time.Time{})
	}
}

func transRecover(ctx context.Context, conn net.Conn, funcName string) {
	panicErr := recover()
	if panicErr != nil {
		if conn != nil {
			klog.CtxErrorf(ctx, "KITEX: panic happened in %s, remoteAddress=%s, error=%v\nstack=%s", funcName, conn.RemoteAddr(), panicErr, string(debug.Stack()))
		} else {
			klog.CtxErrorf(ctx, "KITEX: panic happened in %s, error=%v\nstack=%s", funcName, panicErr, string(debug.Stack()))
		}
	}
}
