package test

import (
	"errors"
	"fmt"
	"io"
	"sync"
	"time"

	alf "github.com/envoyproxy/go-control-plane/envoy/data/accesslog/v3"
	accessloggrpc "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3"
)

// AccessLogService buffers access logs from the remote Envoy nodes.
type AccessLogService struct {
	entries []string
	mu      sync.Mutex
}

func (svc *AccessLogService) log(entry string) {
	svc.mu.Lock()
	defer svc.mu.Unlock()
	svc.entries = append(svc.entries, entry)
}

// Dump releases the collected log entries and clears the log entry list.
func (svc *AccessLogService) Dump(f func(string)) {
	svc.mu.Lock()
	defer svc.mu.Unlock()
	for _, entry := range svc.entries {
		f(entry)
	}
	svc.entries = nil
}

// StreamAccessLogs implements the access log service.
func (svc *AccessLogService) StreamAccessLogs(stream accessloggrpc.AccessLogService_StreamAccessLogsServer) error {
	var logName string
	for {
		msg, err := stream.Recv()
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			return err
		}
		if msg.GetIdentifier() != nil {
			logName = msg.GetIdentifier().GetLogName()
		}
		switch entries := msg.GetLogEntries().(type) {
		case *accessloggrpc.StreamAccessLogsMessage_HttpLogs:
			for _, entry := range entries.HttpLogs.GetLogEntry() {
				if entry != nil {
					common := entry.GetCommonProperties()
					req := entry.GetRequest()
					resp := entry.GetResponse()
					if common == nil {
						common = &alf.AccessLogCommon{}
					}
					if req == nil {
						req = &alf.HTTPRequestProperties{}
					}
					if resp == nil {
						resp = &alf.HTTPResponseProperties{}
					}
					svc.log(fmt.Sprintf("[%s%s] %s %s %s %d %s %s",
						logName, time.Now().Format(time.RFC3339), req.GetAuthority(), req.GetPath(), req.GetScheme(),
						resp.GetResponseCode().GetValue(), req.GetRequestId(), common.GetUpstreamCluster()))
				}
			}
		case *accessloggrpc.StreamAccessLogsMessage_TcpLogs:
			for _, entry := range entries.TcpLogs.GetLogEntry() {
				if entry != nil {
					common := entry.GetCommonProperties()
					if common == nil {
						common = &alf.AccessLogCommon{}
					}
					svc.log(fmt.Sprintf("[%s%s] tcp %s %s",
						logName, time.Now().Format(time.RFC3339), common.GetUpstreamLocalAddress(), common.GetUpstreamCluster()))
				}
			}
		}
	}

	return nil
}
