package xray

import (
	"encoding/json"
	"sync"
)

type segment struct {
	sync.Mutex
	parent           *segment
	openSegments     int
	sampled          bool
	requestWasTraced bool // Used by xray.RequestWasTraced

	// 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  *awsData  `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
}

func (s *segment) cause() *causeData {
	if s.Cause == nil {
		s.Cause = &causeData{}
	}
	return s.Cause
}

func (s *segment) http() *httpData {
	if s.HTTP == nil {
		s.HTTP = &httpData{}
	}
	return s.HTTP
}

func (s *segment) aws() *awsData {
	if s.AWS == nil {
		s.AWS = &awsData{}
	}
	return s.AWS
}

func (s *segment) service() *serviceData {
	if s.Service == nil {
		s.Service = &serviceData{}
	}
	return s.Service
}

func (s *segment) sql() *sqlData {
	if s.SQL == nil {
		s.SQL = &sqlData{}
	}
	return s.SQL
}

type causeData struct {
	WorkingDirectory string      `json:"working_directory,omitempty"`
	Paths            []string    `json:"paths,omitempty"`
	Exceptions       []exception `json:"exceptions,omitempty"`
}

type httpData struct {
	Request  *requestData  `json:"request,omitempty"`
	Response *responseData `json:"response,omitempty"`
}

func (d *httpData) request() *requestData {
	if d.Request == nil {
		d.Request = &requestData{}
	}
	return d.Request
}

func (d *httpData) response() *responseData {
	if d.Response == nil {
		d.Response = &responseData{}
	}
	return d.Response
}

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"`
}

type responseData struct {
	Status        int `json:"status,omitempty"`
	ContentLength int `json:"content_length,omitempty"`
}

type awsData struct {
	AccountID        string       `json:"account_id,omitempty"`
	EC2              *ec2Data     `json:"ec2,omitempty"`
	ElasticBeanstalk *ebData      `json:"elastic_beanstalk,omitempty"`
	ECS              *ecsData     `json:"ecs,omitempty"`
	Tracing          *tracingData `json:"tracing,omitempty"`
	Operation        string       `json:"operation,omitempty"`
	Region           string       `json:"region,omitempty"`
	RequestID        string       `json:"request_id,omitempty"`
	Retries          int          `json:"retries,omitempty"`
	QueueURL         string       `json:"queue_url,omitempty"`
	TableName        string       `json:"table_name,omitempty"`
}

func (d *awsData) ec2() *ec2Data {
	if d.EC2 == nil {
		d.EC2 = &ec2Data{}
	}
	return d.EC2
}

func (d *awsData) elasticBeanstalk() *ebData {
	if d.ElasticBeanstalk == nil {
		d.ElasticBeanstalk = &ebData{}
	}
	return d.ElasticBeanstalk
}

func (d *awsData) ecs() *ecsData {
	if d.ECS == nil {
		d.ECS = &ecsData{}
	}
	return d.ECS
}

func (d *awsData) tracing() *tracingData {
	if d.Tracing == nil {
		d.Tracing = &tracingData{}
	}
	return d.Tracing
}

type ec2Data struct {
	InstanceID       string `json:"instance_id,omitempty"`
	AvailabilityZone string `json:"availability_zone,omitempty"`
}

type ebData struct {
	Environment  string `json:"environment,omitempty"`
	VersionLabel string `json:"version_label,omitempty"`
	DeploymentID int    `json:"deployment_id,omitempty"`
}

type ecsData struct {
	Container string `json:"container,omitempty"`
}

type tracingData struct {
	SDK string `json:"sdk,omitempty"`
}

type serviceData struct {
	Version string `json:"version,omitempty"`
}

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"`
}
