// Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at
//
//     http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 xray

import (
	"context"
	"encoding/json"
	"sync"

	"github.com/aws/aws-xray-sdk-go/v2/header"
	"github.com/aws/aws-xray-sdk-go/v2/strategy/exception"
	"github.com/aws/aws-xray-sdk-go/v2/strategy/sampling"
)

// Segment provides the resource's name, details about the request, and details about the work done.
type Segment struct {
	sync.RWMutex
	parent           *Segment
	openSegments     int
	totalSubSegments uint32
	Sampled          bool           `json:"-"`
	RequestWasTraced bool           `json:"-"` // Used by xray.RequestWasTraced
	ContextDone      bool           `json:"-"`
	Emitted          bool           `json:"-"`
	IncomingHeader   *header.Header `json:"-"`
	ParentSegment    *Segment       `json:"-"` // The root of the Segment tree, the parent Segment (not Subsegment).

	// cancels the context bound to this Segment, after Segment is closed
	cancelCtx context.CancelFunc

	// Required
	TraceID   string  `json:"trace_id,omitempty"`
	ID        string  `json:"id"`
	Name      string  `json:"name"`
	StartTime float64 `json:"start_time"`
	EndTime   float64 `json:"end_time,omitempty"`

	// Optional
	InProgress  bool       `json:"in_progress,omitempty"`
	ParentID    string     `json:"parent_id,omitempty"`
	Fault       bool       `json:"fault,omitempty"`
	Error       bool       `json:"error,omitempty"`
	Throttle    bool       `json:"throttle,omitempty"`
	Cause       *CauseData `json:"cause,omitempty"`
	ResourceARN string     `json:"resource_arn,omitempty"`
	Origin      string     `json:"origin,omitempty"`

	Type         string   `json:"type,omitempty"`
	Namespace    string   `json:"namespace,omitempty"`
	User         string   `json:"user,omitempty"`
	PrecursorIDs []string `json:"precursor_ids,omitempty"`

	HTTP *HTTPData              `json:"http,omitempty"`
	AWS  map[string]interface{} `json:"aws,omitempty"`

	Service *ServiceData `json:"service,omitempty"`

	// SQL
	SQL *SQLData `json:"sql,omitempty"`

	// Metadata
	Annotations map[string]interface{}            `json:"annotations,omitempty"`
	Metadata    map[string]map[string]interface{} `json:"metadata,omitempty"`

	// Children
	Subsegments    []json.RawMessage `json:"subsegments,omitempty"`
	rawSubsegments []*Segment

	// Configuration
	Configuration *Config `json:"-"`

	// Lambda
	Facade bool `json:"-"`

	// Dummy Segment flag
	Dummy bool
}

// CauseData provides the shape for unmarshalling data that records exception.
type CauseData struct {
	WorkingDirectory string                `json:"working_directory,omitempty"`
	Paths            []string              `json:"paths,omitempty"`
	Exceptions       []exception.Exception `json:"exceptions,omitempty"`
}

// HTTPData provides the shape for unmarshalling request and response data.
type HTTPData struct {
	Request  *RequestData  `json:"request,omitempty"`
	Response *ResponseData `json:"response,omitempty"`
}

// RequestData provides the shape for unmarshalling request data.
type RequestData struct {
	Method        string `json:"method,omitempty"`
	URL           string `json:"url,omitempty"` // http(s)://host/path
	ClientIP      string `json:"client_ip,omitempty"`
	UserAgent     string `json:"user_agent,omitempty"`
	XForwardedFor bool   `json:"x_forwarded_for,omitempty"`
	Traced        bool   `json:"traced,omitempty"`
}

// ResponseData provides the shape for unmarshalling response data.
type ResponseData struct {
	Status        int `json:"status,omitempty"`
	ContentLength int `json:"content_length,omitempty"`
}

// ServiceData provides the shape for unmarshalling service version.
type ServiceData struct {
	Version        string `json:"version,omitempty"`
	RuntimeVersion string `json:"runtime_version,omitempty"`
	Runtime        string `json:"runtime,omitempty"`
}

// SQLData provides the shape for unmarshalling sql data.
type SQLData struct {
	ConnectionString string `json:"connection_string,omitempty"`
	URL              string `json:"url,omitempty"` // host:port/database
	DatabaseType     string `json:"database_type,omitempty"`
	DatabaseVersion  string `json:"database_version,omitempty"`
	DriverVersion    string `json:"driver_version,omitempty"`
	User             string `json:"user,omitempty"`
	Preparation      string `json:"preparation,omitempty"` // "statement" / "call"
	SanitizedQuery   string `json:"sanitized_query,omitempty"`
}

// DownstreamHeader returns a header for passing to downstream calls.
func (s *Segment) DownstreamHeader() *header.Header {
	r := &header.Header{}

	// If SDK is disabled then return with an empty header
	if SdkDisabled() {
		return r
	}

	if parent := s.ParentSegment.IncomingHeader; parent != nil {
		*r = *parent // copy parent incoming header
	}
	if r.TraceID == "" {
		r.TraceID = s.ParentSegment.TraceID
	}
	if s.ParentSegment.Sampled {
		r.SamplingDecision = header.Sampled
	} else {
		r.SamplingDecision = header.NotSampled
	}
	r.ParentID = s.ID
	return r
}

// GetCause returns value of Cause.
func (s *Segment) GetCause() *CauseData {
	if s.Cause == nil {
		s.Cause = &CauseData{}
	}
	return s.Cause
}

// GetHTTP returns value of HTTP.
func (s *Segment) GetHTTP() *HTTPData {
	if s.HTTP == nil {
		s.HTTP = &HTTPData{}
	}
	return s.HTTP
}

// GetAWS returns value of AWS.
func (s *Segment) GetAWS() map[string]interface{} {
	if s.AWS == nil {
		s.AWS = make(map[string]interface{})
	}
	return s.AWS
}

// GetService returns value of Service.
func (s *Segment) GetService() *ServiceData {
	if s.Service == nil {
		s.Service = &ServiceData{}
	}
	return s.Service
}

// GetSQL returns value of SQL.
func (s *Segment) GetSQL() *SQLData {
	if s.SQL == nil {
		s.SQL = &SQLData{}
	}
	return s.SQL
}

// GetRequest returns value of RequestData.
func (d *HTTPData) GetRequest() *RequestData {
	if d.Request == nil {
		d.Request = &RequestData{}
	}
	return d.Request
}

// GetResponse returns value of ResponseData.
func (d *HTTPData) GetResponse() *ResponseData {
	if d.Response == nil {
		d.Response = &ResponseData{}
	}
	return d.Response
}

// GetConfiguration returns a value of Config.
func (s *Segment) GetConfiguration() *Config {
	if s.Configuration == nil {
		s.Configuration = &Config{}
	}
	return s.Configuration
}

// AddRuleName adds rule name, if present from sampling decision to xray context.
func (s *Segment) AddRuleName(sd *sampling.Decision) {
	if sd.Rule != nil {
		sdk := s.GetAWS()["xray"].(SDK)
		sdk.RuleName = *sd.Rule
		s.GetAWS()["xray"] = sdk
	}
}
