package memory

import (
	"context"
	"time"

	"go.unistack.org/micro/v3/tracer"
	"go.unistack.org/micro/v3/util/id"
)

var _ tracer.Tracer = (*Tracer)(nil)

type Tracer struct {
	opts  tracer.Options
	spans []tracer.Span
}

func (t *Tracer) Spans() []tracer.Span {
	return t.spans
}

func (t *Tracer) Start(ctx context.Context, name string, opts ...tracer.SpanOption) (context.Context, tracer.Span) {
	options := tracer.NewSpanOptions(opts...)
	span := &Span{
		name:      name,
		ctx:       ctx,
		tracer:    t,
		labels:    options.Labels,
		kind:      options.Kind,
		startTime: time.Now(),
	}
	span.spanID.s, _ = id.New()
	span.traceID.s, _ = id.New()
	if span.ctx == nil {
		span.ctx = context.Background()
	}
	t.spans = append(t.spans, span)
	return tracer.NewSpanContext(ctx, span), span
}

type memoryStringer struct {
	s string
}

func (s memoryStringer) String() string {
	return s.s
}

func (t *Tracer) Flush(_ context.Context) error {
	return nil
}

func (t *Tracer) Init(opts ...tracer.Option) error {
	for _, o := range opts {
		o(&t.opts)
	}
	return nil
}

func (t *Tracer) Name() string {
	return t.opts.Name
}

type Span struct {
	ctx        context.Context
	tracer     tracer.Tracer
	name       string
	statusMsg  string
	startTime  time.Time
	finishTime time.Time
	traceID    memoryStringer
	spanID     memoryStringer
	events     []*Event
	labels     []interface{}
	logs       []interface{}
	kind       tracer.SpanKind
	status     tracer.SpanStatus
}

func (s *Span) Finish(_ ...tracer.SpanOption) {
	s.finishTime = time.Now()
}

func (s *Span) Context() context.Context {
	return s.ctx
}

func (s *Span) Tracer() tracer.Tracer {
	return s.tracer
}

type Event struct {
	name   string
	labels []interface{}
}

func (s *Span) AddEvent(name string, opts ...tracer.EventOption) {
	options := tracer.NewEventOptions(opts...)
	s.events = append(s.events, &Event{name: name, labels: options.Labels})
}

func (s *Span) SetName(name string) {
	s.name = name
}

func (s *Span) AddLogs(kv ...interface{}) {
	s.logs = append(s.logs, kv...)
}

func (s *Span) AddLabels(kv ...interface{}) {
	s.labels = append(s.labels, kv...)
}

func (s *Span) Kind() tracer.SpanKind {
	return s.kind
}

func (s *Span) TraceID() string {
	return s.traceID.String()
}

func (s *Span) SpanID() string {
	return s.spanID.String()
}

func (s *Span) Status() (tracer.SpanStatus, string) {
	return s.status, s.statusMsg
}

func (s *Span) SetStatus(st tracer.SpanStatus, msg string) {
	s.status = st
	s.statusMsg = msg
}

// NewTracer returns new memory tracer
func NewTracer(opts ...tracer.Option) *Tracer {
	return &Tracer{
		opts: tracer.NewOptions(opts...),
	}
}