package nginx_plus_api

import (
	"fmt"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/influxdata/telegraf/testutil"
)

const processesPayload = `
{
	"respawned": 0
}
`

const connectionsPayload = `
{
	"accepted": 1234567890000,
	"dropped":  2345678900000,
	"active":   345,
	"idle":     567
}
`

const slabsPayload = `
{
  "zone1":{
    "pages":{
      "used":7,
      "free":56
    },
    "slots":{
      "8":{
        "used":1,
        "free":503,
        "reqs":1,
        "fails":0
      },
      "16":{
        "used":1,
        "free":253,
        "reqs":1,
        "fails":0
      },
      "32":{
        "used":3,
        "free":124,
        "reqs":3,
        "fails":0
      },
      "64":{
        "used":3,
        "free":61,
        "reqs":3,
        "fails":0
      },
      "128":{
        "used":6,
        "free":26,
        "reqs":6,
        "fails":0
      },
      "256":{
        "used":0,
        "free":0,
        "reqs":0,
        "fails":0
      },
      "512":{
        "used":2,
        "free":6,
        "reqs":2,
        "fails":0
      },
      "1024":{
        "used":2,
        "free":2,
        "reqs":2,
        "fails":0
      },
      "2048":{
        "used":0,
        "free":0,
        "reqs":0,
        "fails":0
      }
    }
  },
  "zone2":{
    "pages":{
      "used":2218,
      "free":252290
    },
    "slots":{
      "8":{
        "used":1,
        "free":503,
        "reqs":4,
        "fails":0
      },
      "16":{
        "used":0,
        "free":0,
        "reqs":0,
        "fails":0
      },
      "32":{
        "used":8,
        "free":119,
        "reqs":98,
        "fails":0
      },
      "64":{
        "used":10899,
        "free":45,
        "reqs":124255,
        "fails":0
      },
      "128":{
        "used":1,
        "free":31,
        "reqs":1,
        "fails":0
      },
      "256":{
        "used":10901,
        "free":11,
        "reqs":124270,
        "fails":0
      },
      "512":{
        "used":10893,
        "free":3,
        "reqs":124245,
        "fails":0
      },
      "1024":{
        "used":0,
        "free":0,
        "reqs":0,
        "fails":0
      },
      "2048":{
        "used":0,
        "free":0,
        "reqs":10,
        "fails":0
      }
    }
  }
}
`

const sslPayload = `
{
	"handshakes": 79572,
	"handshakes_failed": 21025,
	"session_reuses": 15762
}
`

const resolverZonesPayload = `
{
  "resolver_zone1": {
    "requests": {
      "name": 25460,
      "srv": 130,
      "addr": 2580
    },
    "responses": {
      "noerror": 26499,
      "formerr": 0,
      "servfail": 3,
      "nxdomain": 0,
      "notimp": 0,
      "refused": 0,
      "timedout": 243,
      "unknown": 478
    }
  },
  "resolver_zone2": {
    "requests": {
      "name": 325460,
      "srv": 1130,
      "addr": 12580
    },
    "responses": {
      "noerror": 226499,
      "formerr": 0,
      "servfail": 283,
      "nxdomain": 0,
      "notimp": 0,
      "refused": 0,
      "timedout": 743,
      "unknown": 1478
    }
  }
}
`

const httpRequestsPayload = `
{
	"total": 10624511,
	"current": 4
}
`

const httpServerZonesPayload = `
{
	"site1": {
		"processing": 2,
		"requests": 736395,
		"responses": {
			"1xx": 0,
			"2xx": 727290,
			"3xx": 4614,
			"4xx": 934,
			"5xx": 1535,
			"total": 734373
		},
		"discarded": 2020,
		"received": 180157219,
		"sent": 20183175459
	},
	"site2": {
		"processing": 1,
		"requests": 185307,
		"responses": {
			"1xx": 0,
			"2xx": 112674,
			"3xx": 45383,
			"4xx": 2504,
			"5xx": 4419,
			"total": 164980
		},
		"discarded": 20326,
		"received": 51575327,
		"sent": 2983241510
	}
}
`

const httpLimitReqsPayload = `
{
        "limit_1": {
                "passed": 2,
                "delayed": 9,
                "rejected": 4,
                "delayed_dry_run": 100,
                "rejected_dry_run": 330
        },
        "limit_2": {
                "passed": 451,
                "delayed": 10,
                "rejected": 0,
                "delayed_dry_run": 10,
                "rejected_dry_run": 32
        }
}
`

const httpLocationZonesPayload = `
{
  "site1": {
    "requests": 736395,
    "responses": {
      "1xx": 0,
      "2xx": 727290,
      "3xx": 4614,
      "4xx": 934,
      "5xx": 1535,
      "total": 734373
    },
    "discarded": 2020,
    "received": 180157219,
    "sent": 20183175459
  },
  "site2": {
    "requests": 185307,
    "responses": {
      "1xx": 0,
      "2xx": 112674,
      "3xx": 45383,
      "4xx": 2504,
      "5xx": 4419,
      "total": 164980
    },
    "discarded": 20326,
    "received": 51575327,
    "sent": 2983241510
  }
}
`

const httpUpstreamsPayload = `
{
	"trac-backend": {
		"peers": [
		{
			"id": 0,
			"server": "10.0.0.1:8088",
			"name": "10.0.0.1:8088",
			"backup": false,
			"weight": 5,
			"state": "up",
			"active": 0,
			"requests": 667231,
			"header_time": 20,
			"response_time": 36,
			"responses": {
				"1xx": 0,
				"2xx": 666310,
				"3xx": 0,
				"4xx": 915,
				"5xx": 6,
				"total": 667231
			},
			"sent": 251946292,
			"received": 19222475454,
			"fails": 0,
			"unavail": 0,
			"health_checks": {
				"checks": 26214,
				"fails": 0,
				"unhealthy": 0,
				"last_passed": true
			},
			"downtime": 0,
			"downstart": {},
			"selected": {}
		},
		{
			"id": 1,
			"server": "10.0.0.1:8089",
			"name": "10.0.0.1:8089",
			"backup": true,
			"weight": 1,
			"state": "unhealthy",
			"active": 0,
			"requests": 0,
			"responses": {
				"1xx": 0,
				"2xx": 0,
				"3xx": 0,
				"4xx": 0,
				"5xx": 0,
				"total": 0
			},
			"sent": 0,
			"received": 0,
			"fails": 0,
			"unavail": 0,
			"health_checks": {
				"checks": 26284,
				"fails": 26284,
				"unhealthy": 1,
				"last_passed": false
			},
			"downtime": 262925617,
			"downstart": {},
			"selected": {}
		}
		],
		"keepalive": 0,
		"zombies": 0,
		"zone": "trac-backend"
	},
	"hg-backend": {
		"peers": [
		{
			"id": 0,
			"server": "10.0.0.1:8088",
			"name": "10.0.0.1:8088",
			"backup": false,
			"weight": 5,
			"state": "up",
			"active": 0,
			"requests": 667231,
			"header_time": 20,
			"response_time": 36,
			"responses": {
				"1xx": 0,
				"2xx": 666310,
				"3xx": 0,
				"4xx": 915,
				"5xx": 6,
				"total": 667231
			},
			"sent": 251946292,
			"received": 19222475454,
			"fails": 0,
			"unavail": 0,
			"health_checks": {
				"checks": 26214,
				"fails": 0,
				"unhealthy": 0,
				"last_passed": true
			},
			"downtime": 0,
			"downstart": {},
			"selected": {}
		},
		{
			"id": 1,
			"server": "10.0.0.1:8089",
			"name": "10.0.0.1:8089",
			"backup": true,
			"weight": 1,
			"state": "unhealthy",
			"active": 0,
			"requests": 0,
			"responses": {
				"1xx": 0,
				"2xx": 0,
				"3xx": 0,
				"4xx": 0,
				"5xx": 0,
				"total": 0
			},
			"sent": 0,
			"received": 0,
			"fails": 0,
			"unavail": 0,
			"health_checks": {
				"checks": 26284,
				"fails": 26284,
				"unhealthy": 1,
				"last_passed": false
			},
			"downtime": 262925617,
			"downstart": {},
			"selected": {}
		}
		],
		"keepalive": 0,
		"zombies": 0,
		"zone": "hg-backend"
	}
}
`

const httpCachesPayload = `
{
	"http-cache": {
		"size": 530915328,
		"max_size": 536870912,
		"cold": false,
		"hit": {
			"responses": 254032,
			"bytes": 6685627875
		},
		"stale": {
			"responses": 0,
			"bytes": 0
		},
		"updating": {
			"responses": 0,
			"bytes": 0
		},
		"revalidated": {
			"responses": 0,
			"bytes": 0
		},
		"miss": {
			"responses": 1619201,
			"bytes": 53841943822
		},
		"expired": {
			"responses": 45859,
			"bytes": 1656847080,
			"responses_written": 44992,
			"bytes_written": 1641825173
		},
		"bypass": {
			"responses": 200187,
			"bytes": 5510647548,
			"responses_written": 200173,
			"bytes_written": 44992
		}
	},
	"frontend-cache": {
		"size": 530915328,
		"max_size": 536870912,
		"cold": false,
		"hit": {
			"responses": 254032,
			"bytes": 6685627875
		},
		"stale": {
			"responses": 0,
			"bytes": 0
		},
		"updating": {
			"responses": 0,
			"bytes": 0
		},
		"revalidated": {
			"responses": 0,
			"bytes": 0
		},
		"miss": {
			"responses": 1619201,
			"bytes": 53841943822
		},
		"expired": {
			"responses": 45859,
			"bytes": 1656847080,
			"responses_written": 44992,
			"bytes_written": 1641825173
		},
		"bypass": {
			"responses": 200187,
			"bytes": 5510647548,
			"responses_written": 200173,
			"bytes_written": 44992
		}
	}
}
`

const streamUpstreamsPayload = `
{
	"mysql_backends": {
		"peers": [
		{
			"id": 0,
			"server": "10.0.0.1:12345",
			"name": "10.0.0.1:12345",
			"backup": false,
			"weight": 5,
			"state": "up",
			"active": 0,
			"max_conns": 30,
			"connecions": 1231,
			"sent": 251946292,
			"received": 19222475454,
			"fails": 0,
			"unavail": 0,
			"health_checks": {
				"checks": 26214,
				"fails": 0,
				"unhealthy": 0,
				"last_passed": true
			},
			"downtime": 0,
			"downstart": {},
			"selected": {}
		},
		{
			"id": 1,
			"server": "10.0.0.1:12346",
			"name": "10.0.0.1:12346",
			"backup": true,
			"weight": 1,
			"state": "unhealthy",
			"active": 0,
			"max_conns": 30,
			"connections": 0,
			"sent": 0,
			"received": 0,
			"fails": 0,
			"unavail": 0,
			"health_checks": {
				"checks": 26284,
				"fails": 26284,
				"unhealthy": 1,
				"last_passed": false
			},
			"downtime": 262925617,
			"downstart": {},
			"selected": {}
		}
		],
		"zombies": 0,
		"zone": "mysql_backends"
	},
	"dns": {
		"peers": [
		{
			"id": 0,
			"server": "10.0.0.1:12347",
			"name": "10.0.0.1:12347",
			"backup": false,
			"weight": 5,
			"state": "up",
			"active": 0,
			"max_conns": 30,
			"connections": 667231,
			"sent": 251946292,
			"received": 19222475454,
			"fails": 0,
			"unavail": 0,
			"health_checks": {
				"checks": 26214,
				"fails": 0,
				"unhealthy": 0,
				"last_passed": true
			},
			"downtime": 0,
			"downstart": {},
			"selected": {}
		},
		{
			"id": 1,
			"server": "10.0.0.1:12348",
			"name": "10.0.0.1:12348",
			"backup": true,
			"weight": 1,
			"state": "unhealthy",
			"active": 0,
			"connections": 0,
			"max_conns": 30,
			"sent": 0,
			"received": 0,
			"fails": 0,
			"unavail": 0,
			"health_checks": {
				"checks": 26284,
				"fails": 26284,
				"unhealthy": 1,
				"last_passed": false
			},
			"downtime": 262925617,
			"downstart": {},
			"selected": {}
		}
		],
		"zombies": 0,
		"zone": "dns"
	}
}
`

const streamServerZonesPayload = `
{
	"mysql-frontend": {
		"processing": 2,
		"connections": 270925,
		"sessions": {
			"2xx": 155564,
			"4xx": 0,
			"5xx": 0,
			"total": 270925
		},
		"discarded": 0,
		"received": 28988975,
		"sent": 3879346317
	},
	"dns": {
		"processing": 1,
		"connections": 155569,
		"sessions": {
			"2xx": 155564,
			"4xx": 0,
			"5xx": 0,
			"total": 155569
		},
		"discarded": 0,
		"received": 4200363,
		"sent": 20489184
	}
}
`

func TestGatherProcessesMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, processesPath, processesPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherProcessesMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_processes",
		map[string]interface{}{
			"respawned": int(0),
		},
		map[string]string{
			"source": host,
			"port":   port,
		})
}

func TestGatherConnectionsMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, connectionsPath, connectionsPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherConnectionsMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_connections",
		map[string]interface{}{
			"accepted": int64(1234567890000),
			"dropped":  int64(2345678900000),
			"active":   int64(345),
			"idle":     int64(567),
		},
		map[string]string{
			"source": host,
			"port":   port,
		})
}

func TestGatherSlabsMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, slabsPath, slabsPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherSlabsMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_slabs_pages",
		map[string]interface{}{
			"used": int64(7),
			"free": int64(56),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "zone1",
		})
	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_slabs_pages",
		map[string]interface{}{
			"used": int64(2218),
			"free": int64(252290),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "zone2",
		})
	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_slabs_slots",
		map[string]interface{}{
			"used":  int64(1),
			"free":  int64(503),
			"reqs":  int64(1),
			"fails": int64(0),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "zone1",
			"slot":   "8",
		})
	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_slabs_slots",
		map[string]interface{}{
			"used":  int64(10893),
			"free":  int64(3),
			"reqs":  int64(124245),
			"fails": int64(0),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "zone2",
			"slot":   "512",
		})
}

func TestGatherSslMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, sslPath, sslPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherSslMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_ssl",
		map[string]interface{}{
			"handshakes":        int64(79572),
			"handshakes_failed": int64(21025),
			"session_reuses":    int64(15762),
		},
		map[string]string{
			"source": host,
			"port":   port,
		})
}

func TestGatherHttpRequestsMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, httpRequestsPath, httpRequestsPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherHTTPRequestsMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_requests",
		map[string]interface{}{
			"total":   int64(10624511),
			"current": int64(4),
		},
		map[string]string{
			"source": host,
			"port":   port,
		})
}

func TestGatherHttpServerZonesMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, httpServerZonesPath, httpServerZonesPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherHTTPServerZonesMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_server_zones",
		map[string]interface{}{
			"discarded":       int64(2020),
			"processing":      int(2),
			"received":        int64(180157219),
			"requests":        int64(736395),
			"responses_1xx":   int64(0),
			"responses_2xx":   int64(727290),
			"responses_3xx":   int64(4614),
			"responses_4xx":   int64(934),
			"responses_5xx":   int64(1535),
			"responses_total": int64(734373),
			"sent":            int64(20183175459),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "site1",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_server_zones",
		map[string]interface{}{
			"discarded":       int64(20326),
			"processing":      int(1),
			"received":        int64(51575327),
			"requests":        int64(185307),
			"responses_1xx":   int64(0),
			"responses_2xx":   int64(112674),
			"responses_3xx":   int64(45383),
			"responses_4xx":   int64(2504),
			"responses_5xx":   int64(4419),
			"responses_total": int64(164980),
			"sent":            int64(2983241510),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "site2",
		})
}

func TestGatherHttpLimitReqsMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, httpLimitReqsPath, httpLimitReqsPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherHTTPLimitReqsMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_limit_reqs",
		map[string]interface{}{
			"passed":           int64(2),
			"delayed":          int64(9),
			"rejected":         int64(4),
			"delayed_dry_run":  int64(100),
			"rejected_dry_run": int64(330),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"limit":  "limit_1",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_limit_reqs",
		map[string]interface{}{
			"passed":           int64(451),
			"delayed":          int64(10),
			"rejected":         int64(0),
			"delayed_dry_run":  int64(10),
			"rejected_dry_run": int64(32),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"limit":  "limit_2",
		})
}

func TestGatherHttpLocationZonesMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, httpLocationZonesPath, httpLocationZonesPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherHTTPLocationZonesMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_location_zones",
		map[string]interface{}{
			"discarded":       int64(2020),
			"received":        int64(180157219),
			"requests":        int64(736395),
			"responses_1xx":   int64(0),
			"responses_2xx":   int64(727290),
			"responses_3xx":   int64(4614),
			"responses_4xx":   int64(934),
			"responses_5xx":   int64(1535),
			"responses_total": int64(734373),
			"sent":            int64(20183175459),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "site1",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_location_zones",
		map[string]interface{}{
			"discarded":       int64(20326),
			"received":        int64(51575327),
			"requests":        int64(185307),
			"responses_1xx":   int64(0),
			"responses_2xx":   int64(112674),
			"responses_3xx":   int64(45383),
			"responses_4xx":   int64(2504),
			"responses_5xx":   int64(4419),
			"responses_total": int64(164980),
			"sent":            int64(2983241510),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "site2",
		})
}

func TestGatherHttpUpstreamsMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, httpUpstreamsPath, httpUpstreamsPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherHTTPUpstreamsMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_upstreams",
		map[string]interface{}{
			"keepalive": int(0),
			"zombies":   int(0),
		},
		map[string]string{
			"source":   host,
			"port":     port,
			"upstream": "trac-backend",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_upstreams",
		map[string]interface{}{
			"keepalive": int(0),
			"zombies":   int(0),
		},
		map[string]string{
			"source":   host,
			"port":     port,
			"upstream": "hg-backend",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_upstream_peers",
		map[string]interface{}{
			"active":                   int(0),
			"backup":                   false,
			"downtime":                 int64(0),
			"fails":                    int64(0),
			"header_time":              int64(20),
			"healthchecks_checks":      int64(26214),
			"healthchecks_fails":       int64(0),
			"healthchecks_last_passed": true,
			"healthchecks_unhealthy":   int64(0),
			"received":                 int64(19222475454),
			"requests":                 int64(667231),
			"response_time":            int64(36),
			"responses_1xx":            int64(0),
			"responses_2xx":            int64(666310),
			"responses_3xx":            int64(0),
			"responses_4xx":            int64(915),
			"responses_5xx":            int64(6),
			"responses_total":          int64(667231),
			"sent":                     int64(251946292),
			"state":                    "up",
			"unavail":                  int64(0),
			"weight":                   int(5),
		},
		map[string]string{
			"source":           host,
			"port":             port,
			"upstream":         "trac-backend",
			"upstream_address": "10.0.0.1:8088",
			"id":               "0",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_upstream_peers",
		map[string]interface{}{
			"active":                   int(0),
			"backup":                   true,
			"downtime":                 int64(262925617),
			"fails":                    int64(0),
			"healthchecks_checks":      int64(26284),
			"healthchecks_fails":       int64(26284),
			"healthchecks_last_passed": false,
			"healthchecks_unhealthy":   int64(1),
			"received":                 int64(0),
			"requests":                 int64(0),
			"responses_1xx":            int64(0),
			"responses_2xx":            int64(0),
			"responses_3xx":            int64(0),
			"responses_4xx":            int64(0),
			"responses_5xx":            int64(0),
			"responses_total":          int64(0),
			"sent":                     int64(0),
			"state":                    "unhealthy",
			"unavail":                  int64(0),
			"weight":                   int(1),
		},
		map[string]string{
			"source":           host,
			"port":             port,
			"upstream":         "trac-backend",
			"upstream_address": "10.0.0.1:8089",
			"id":               "1",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_upstream_peers",
		map[string]interface{}{
			"active":                   int(0),
			"backup":                   false,
			"downtime":                 int64(0),
			"fails":                    int64(0),
			"header_time":              int64(20),
			"healthchecks_checks":      int64(26214),
			"healthchecks_fails":       int64(0),
			"healthchecks_last_passed": true,
			"healthchecks_unhealthy":   int64(0),
			"received":                 int64(19222475454),
			"requests":                 int64(667231),
			"response_time":            int64(36),
			"responses_1xx":            int64(0),
			"responses_2xx":            int64(666310),
			"responses_3xx":            int64(0),
			"responses_4xx":            int64(915),
			"responses_5xx":            int64(6),
			"responses_total":          int64(667231),
			"sent":                     int64(251946292),
			"state":                    "up",
			"unavail":                  int64(0),
			"weight":                   int(5),
		},
		map[string]string{
			"source":           host,
			"port":             port,
			"upstream":         "hg-backend",
			"upstream_address": "10.0.0.1:8088",
			"id":               "0",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_upstream_peers",
		map[string]interface{}{
			"active":                   int(0),
			"backup":                   true,
			"downtime":                 int64(262925617),
			"fails":                    int64(0),
			"healthchecks_checks":      int64(26284),
			"healthchecks_fails":       int64(26284),
			"healthchecks_last_passed": false,
			"healthchecks_unhealthy":   int64(1),
			"received":                 int64(0),
			"requests":                 int64(0),
			"responses_1xx":            int64(0),
			"responses_2xx":            int64(0),
			"responses_3xx":            int64(0),
			"responses_4xx":            int64(0),
			"responses_5xx":            int64(0),
			"responses_total":          int64(0),
			"sent":                     int64(0),
			"state":                    "unhealthy",
			"unavail":                  int64(0),
			"weight":                   int(1),
		},
		map[string]string{
			"source":           host,
			"port":             port,
			"upstream":         "hg-backend",
			"upstream_address": "10.0.0.1:8089",
			"id":               "1",
		})
}

func TestGatherHttpCachesMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, httpCachesPath, httpCachesPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherHTTPCachesMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_caches",
		map[string]interface{}{
			"bypass_bytes":              int64(5510647548),
			"bypass_bytes_written":      int64(44992),
			"bypass_responses":          int64(200187),
			"bypass_responses_written":  int64(200173),
			"cold":                      false,
			"expired_bytes":             int64(1656847080),
			"expired_bytes_written":     int64(1641825173),
			"expired_responses":         int64(45859),
			"expired_responses_written": int64(44992),
			"hit_bytes":                 int64(6685627875),
			"hit_responses":             int64(254032),
			"max_size":                  int64(536870912),
			"miss_bytes":                int64(53841943822),
			"miss_bytes_written":        int64(0),
			"miss_responses":            int64(1619201),
			"miss_responses_written":    int64(0),
			"revalidated_bytes":         int64(0),
			"revalidated_responses":     int64(0),
			"size":                      int64(530915328),
			"stale_bytes":               int64(0),
			"stale_responses":           int64(0),
			"updating_bytes":            int64(0),
			"updating_responses":        int64(0),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"cache":  "http-cache",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_http_caches",
		map[string]interface{}{
			"bypass_bytes":              int64(5510647548),
			"bypass_bytes_written":      int64(44992),
			"bypass_responses":          int64(200187),
			"bypass_responses_written":  int64(200173),
			"cold":                      false,
			"expired_bytes":             int64(1656847080),
			"expired_bytes_written":     int64(1641825173),
			"expired_responses":         int64(45859),
			"expired_responses_written": int64(44992),
			"hit_bytes":                 int64(6685627875),
			"hit_responses":             int64(254032),
			"max_size":                  int64(536870912),
			"miss_bytes":                int64(53841943822),
			"miss_bytes_written":        int64(0),
			"miss_responses":            int64(1619201),
			"miss_responses_written":    int64(0),
			"revalidated_bytes":         int64(0),
			"revalidated_responses":     int64(0),
			"size":                      int64(530915328),
			"stale_bytes":               int64(0),
			"stale_responses":           int64(0),
			"updating_bytes":            int64(0),
			"updating_responses":        int64(0),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"cache":  "frontend-cache",
		})
}

func TestGatherResolverZonesMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, resolverZonesPath, resolverZonesPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherResolverZonesMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_resolver_zones",
		map[string]interface{}{
			"name":     int64(25460),
			"srv":      int64(130),
			"addr":     int64(2580),
			"noerror":  int64(26499),
			"formerr":  int64(0),
			"servfail": int64(3),
			"nxdomain": int64(0),
			"notimp":   int64(0),
			"refused":  int64(0),
			"timedout": int64(243),
			"unknown":  int64(478),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "resolver_zone1",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_resolver_zones",
		map[string]interface{}{
			"name":     int64(325460),
			"srv":      int64(1130),
			"addr":     int64(12580),
			"noerror":  int64(226499),
			"formerr":  int64(0),
			"servfail": int64(283),
			"nxdomain": int64(0),
			"notimp":   int64(0),
			"refused":  int64(0),
			"timedout": int64(743),
			"unknown":  int64(1478),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "resolver_zone2",
		})
}

func TestGatherStreamUpstreams(t *testing.T) {
	ts, n := prepareEndpoint(t, streamUpstreamsPath, streamUpstreamsPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherStreamUpstreamsMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_stream_upstreams",
		map[string]interface{}{
			"zombies": int(0),
		},
		map[string]string{
			"source":   host,
			"port":     port,
			"upstream": "mysql_backends",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_stream_upstreams",
		map[string]interface{}{
			"zombies": int(0),
		},
		map[string]string{
			"source":   host,
			"port":     port,
			"upstream": "dns",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_stream_upstream_peers",
		map[string]interface{}{
			"active":                   int(0),
			"backup":                   false,
			"connections":              int64(0),
			"downtime":                 int64(0),
			"fails":                    int64(0),
			"healthchecks_checks":      int64(26214),
			"healthchecks_fails":       int64(0),
			"healthchecks_last_passed": true,
			"healthchecks_unhealthy":   int64(0),
			"received":                 int64(19222475454),
			"sent":                     int64(251946292),
			"state":                    "up",
			"unavail":                  int64(0),
			"weight":                   int(5),
		},
		map[string]string{
			"source":           host,
			"port":             port,
			"upstream":         "mysql_backends",
			"upstream_address": "10.0.0.1:12345",
			"id":               "0",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_stream_upstream_peers",
		map[string]interface{}{
			"active":                   int(0),
			"backup":                   true,
			"connections":              int64(0),
			"downtime":                 int64(262925617),
			"fails":                    int64(0),
			"healthchecks_checks":      int64(26284),
			"healthchecks_fails":       int64(26284),
			"healthchecks_last_passed": false,
			"healthchecks_unhealthy":   int64(1),
			"received":                 int64(0),
			"sent":                     int64(0),
			"state":                    "unhealthy",
			"unavail":                  int64(0),
			"weight":                   int(1),
		},
		map[string]string{
			"source":           host,
			"port":             port,
			"upstream":         "mysql_backends",
			"upstream_address": "10.0.0.1:12346",
			"id":               "1",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_stream_upstream_peers",
		map[string]interface{}{
			"active":                   int(0),
			"backup":                   false,
			"connections":              int64(667231),
			"downtime":                 int64(0),
			"fails":                    int64(0),
			"healthchecks_checks":      int64(26214),
			"healthchecks_fails":       int64(0),
			"healthchecks_last_passed": true,
			"healthchecks_unhealthy":   int64(0),
			"received":                 int64(19222475454),
			"sent":                     int64(251946292),
			"state":                    "up",
			"unavail":                  int64(0),
			"weight":                   int(5),
		},
		map[string]string{
			"source":           host,
			"port":             port,
			"upstream":         "dns",
			"upstream_address": "10.0.0.1:12347",
			"id":               "0",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_stream_upstream_peers",
		map[string]interface{}{
			"active":                   int(0),
			"backup":                   true,
			"connections":              int64(0),
			"downtime":                 int64(262925617),
			"fails":                    int64(0),
			"healthchecks_checks":      int64(26284),
			"healthchecks_fails":       int64(26284),
			"healthchecks_last_passed": false,
			"healthchecks_unhealthy":   int64(1),
			"received":                 int64(0),
			"sent":                     int64(0),
			"state":                    "unhealthy",
			"unavail":                  int64(0),
			"weight":                   int(1),
		},
		map[string]string{
			"source":           host,
			"port":             port,
			"upstream":         "dns",
			"upstream_address": "10.0.0.1:12348",
			"id":               "1",
		})
}

func TestGatherStreamServerZonesMetrics(t *testing.T) {
	ts, n := prepareEndpoint(t, streamServerZonesPath, streamServerZonesPayload)
	defer ts.Close()

	var acc testutil.Accumulator
	addr, host, port := prepareAddr(t, ts)

	require.NoError(t, n.gatherStreamServerZonesMetrics(addr, &acc))

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_stream_server_zones",
		map[string]interface{}{
			"connections": int(270925),
			"processing":  int(2),
			"received":    int64(28988975),
			"sent":        int64(3879346317),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "mysql-frontend",
		})

	acc.AssertContainsTaggedFields(
		t,
		"nginx_plus_api_stream_server_zones",
		map[string]interface{}{
			"connections": int(155569),
			"processing":  int(1),
			"received":    int64(4200363),
			"sent":        int64(20489184),
		},
		map[string]string{
			"source": host,
			"port":   port,
			"zone":   "dns",
		})
}

func TestUnavailableEndpoints(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusNotFound)
	}))
	defer ts.Close()

	n := &NginxPlusAPI{
		client: ts.Client(),
	}

	addr, err := url.Parse(ts.URL)
	require.NoError(t, err)

	var acc testutil.Accumulator
	n.gatherMetrics(addr, &acc)
	require.NoError(t, acc.FirstError())
}

func TestServerError(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
	}))
	defer ts.Close()

	n := &NginxPlusAPI{
		client: ts.Client(),
	}

	addr, err := url.Parse(ts.URL)
	require.NoError(t, err)

	var acc testutil.Accumulator
	n.gatherMetrics(addr, &acc)
	require.Error(t, acc.FirstError())
}

func TestMalformedJSON(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		if _, err := fmt.Fprintln(w, "this is not JSON"); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			t.Error(err)
			return
		}
	}))
	defer ts.Close()

	n := &NginxPlusAPI{
		client: ts.Client(),
	}

	addr, err := url.Parse(ts.URL)
	require.NoError(t, err)

	var acc testutil.Accumulator
	n.gatherMetrics(addr, &acc)
	require.Error(t, acc.FirstError())
}

func TestUnknownContentType(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "text/plain")
	}))
	defer ts.Close()

	n := &NginxPlusAPI{
		client: ts.Client(),
	}

	addr, err := url.Parse(ts.URL)
	require.NoError(t, err)

	var acc testutil.Accumulator
	n.gatherMetrics(addr, &acc)
	require.Error(t, acc.FirstError())
}

func prepareAddr(t *testing.T, ts *httptest.Server) (addr *url.URL, host, port string) {
	t.Helper()
	addr, err := url.Parse(ts.URL + "/api")
	require.NoError(t, err)

	host, port, err = net.SplitHostPort(addr.Host)
	if err != nil {
		host = addr.Host
		if addr.Scheme == "http" {
			port = "80"
		} else if addr.Scheme == "https" {
			port = "443"
		} else {
			port = ""
		}
	}

	return addr, host, port
}

func prepareEndpoint(t *testing.T, path, payload string) (*httptest.Server, *NginxPlusAPI) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fullPath := fmt.Sprintf("/api/%d/%s", defaultAPIVersion, path)
		if r.URL.Path != fullPath {
			w.WriteHeader(http.StatusInternalServerError)
			t.Errorf("Unknown request path, expected: %q, actual: %q", fullPath, r.URL.Path)
			return
		}

		w.Header()["Content-Type"] = []string{"application/json"}
		if _, err := fmt.Fprintln(w, payload); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			t.Error(err)
			return
		}
	}))

	n := &NginxPlusAPI{
		Urls:       []string{ts.URL + "/api"},
		APIVersion: defaultAPIVersion,
	}

	client, err := n.createHTTPClient()
	require.NoError(t, err)

	n.client = client

	return ts, n
}
