package redash_test

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"regexp"
	"strings"
	"testing"
	"time"

	"github.com/araddon/dateparse"
	"github.com/jarcoal/httpmock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/winebarrel/redash-go/v2"
)

func Test_ListQueries_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		assert.Equal("page=1&page_size=25", req.URL.Query().Encode())
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"count": 1,
				"page": 1,
				"page_size": 25,
				"results": [
					{
						"api_key": "api_key",
						"created_at": "2023-02-10T01:23:45.000Z",
						"data_source_id": 1,
						"description": "description",
						"id": 1,
						"is_archived": false,
						"is_draft": false,
						"is_favorite": false,
						"is_safe": true,
						"last_modified_by_id": 1,
						"latest_query_data_id": 1,
						"name": "my-query",
						"options": {
							"parameters": []
						},
						"query": "select 1",
						"query_hash": "query_hash",
						"retrieved_at": "2023-02-10T01:23:45.000Z",
						"runtime": 0.1,
						"schedule": {
							"day_of_week": null,
							"interval": 60,
							"time": null,
							"until": "2023-02-11"
						},
						"tags": [],
						"updated_at": "2023-02-10T01:23:45.000Z",
						"user": {},
						"version": 1
					}
				]
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.ListQueries(context.Background(), &redash.ListQueriesInput{
		OnlyFavorites: false,
		Page:          1,
		PageSize:      25,
	})
	assert.NoError(err)
	assert.Equal(&redash.QueryPage{
		Count:    1,
		Page:     1,
		PageSize: 25,
		Results: []redash.Query{
			{
				APIKey:            "api_key",
				CanEdit:           false,
				CreatedAt:         dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				DataSourceID:      1,
				Description:       "description",
				ID:                1,
				IsArchived:        false,
				IsDraft:           false,
				IsFavorite:        false,
				IsSafe:            true,
				LastModifiedBy:    nil,
				LastModifiedByID:  1,
				LatestQueryDataID: 1,
				Name:              "my-query",
				Options:           redash.QueryOptions{Parameters: []redash.QueryOptionsParameter{}},
				Query:             "select 1",
				QueryHash:         "query_hash",
				RetrievedAt:       dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				Runtime:           0.1,
				Schedule: &redash.QueueSchedule{
					DayOfWeek: "",
					Interval:  60,
					Time:      "",
					Until:     "2023-02-11",
				},
				Tags:           []string{},
				UpdatedAt:      dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				User:           redash.User{},
				Version:        1,
				Visualizations: nil,
			},
		},
	}, res)
}

func Test_ListQueries_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.ListQueries(context.Background(), &redash.ListQueriesInput{
		OnlyFavorites: false,
		Page:          1,
		PageSize:      25,
	})
	assert.ErrorContains(err, "GET api/queries failed: HTTP status code not OK: 503\nerror")
}

func Test_ListQueries_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.ListQueries(context.Background(), &redash.ListQueriesInput{
		OnlyFavorites: false,
		Page:          1,
		PageSize:      25,
	})
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_ListQueries_WithQ(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		assert.Equal("page=1&page_size=25&q=my-query", req.URL.Query().Encode())
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"count": 1,
				"page": 1,
				"page_size": 25,
				"results": [
					{
						"api_key": "api_key",
						"created_at": "2023-02-10T01:23:45.000Z",
						"data_source_id": 1,
						"description": "description",
						"id": 1,
						"is_archived": false,
						"is_draft": false,
						"is_favorite": false,
						"is_safe": true,
						"last_modified_by_id": 1,
						"latest_query_data_id": 1,
						"name": "my-query",
						"options": {
							"parameters": []
						},
						"query": "select 1",
						"query_hash": "query_hash",
						"retrieved_at": "2023-02-10T01:23:45.000Z",
						"runtime": 0.1,
						"schedule": {
							"day_of_week": null,
							"interval": 60,
							"time": null,
							"until": "2023-02-11"
						},
						"tags": [],
						"updated_at": "2023-02-10T01:23:45.000Z",
						"user": {},
						"version": 1
					}
				]
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.ListQueries(context.Background(), &redash.ListQueriesInput{
		OnlyFavorites: false,
		Page:          1,
		PageSize:      25,
		Q:             "my-query",
	})
	assert.NoError(err)
	assert.Equal(&redash.QueryPage{
		Count:    1,
		Page:     1,
		PageSize: 25,
		Results: []redash.Query{
			{
				APIKey:            "api_key",
				CanEdit:           false,
				CreatedAt:         dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				DataSourceID:      1,
				Description:       "description",
				ID:                1,
				IsArchived:        false,
				IsDraft:           false,
				IsFavorite:        false,
				IsSafe:            true,
				LastModifiedBy:    nil,
				LastModifiedByID:  1,
				LatestQueryDataID: 1,
				Name:              "my-query",
				Options:           redash.QueryOptions{Parameters: []redash.QueryOptionsParameter{}},
				Query:             "select 1",
				QueryHash:         "query_hash",
				RetrievedAt:       dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				Runtime:           0.1,
				Schedule: &redash.QueueSchedule{
					DayOfWeek: "",
					Interval:  60,
					Time:      "",
					Until:     "2023-02-11",
				},
				Tags:           []string{},
				UpdatedAt:      dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				User:           redash.User{},
				Version:        1,
				Visualizations: nil,
			},
		},
	}, res)
}

func Test_GetQuery_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"api_key": "api_key",
				"can_edit": true,
				"created_at": "2023-02-10T01:23:45.000Z",
				"data_source_id": 1,
				"description": "description",
				"id": 1,
				"is_archived": false,
				"is_draft": false,
				"is_favorite": false,
				"is_safe": true,
				"last_modified_by": {},
				"latest_query_data_id": 1,
				"name": "my-query",
				"options": {
					"parameters": []
				},
				"query": "select 1",
				"query_hash": "query_hash",
				"schedule": {
					"day_of_week": null,
					"interval": 60,
					"time": null,
					"until": "2023-02-11"
				},
				"tags": [],
				"updated_at": "2023-02-10T01:23:45.000Z",
				"user": {},
				"version": 1,
				"visualizations": [
					{
						"created_at": "2023-02-10T01:23:45.000Z",
						"description": "description",
						"id": 1,
						"name": "Table",
						"options": {},
						"type": "TABLE",
						"updated_at": "2023-02-10T01:23:45.000Z"
					}
				]
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.GetQuery(context.Background(), 1)
	assert.NoError(err)
	assert.Equal(&redash.Query{
		APIKey:            "api_key",
		CanEdit:           true,
		CreatedAt:         dateparse.MustParse("2023-02-10T01:23:45.000Z"),
		DataSourceID:      1,
		Description:       "description",
		ID:                1,
		IsArchived:        false,
		IsDraft:           false,
		IsFavorite:        false,
		IsSafe:            true,
		LastModifiedBy:    &redash.User{},
		LastModifiedByID:  0,
		LatestQueryDataID: 1,
		Name:              "my-query",
		Options:           redash.QueryOptions{Parameters: []redash.QueryOptionsParameter{}},
		Query:             "select 1",
		QueryHash:         "query_hash",
		RetrievedAt:       time.Time{},
		Runtime:           0,
		Schedule: &redash.QueueSchedule{
			DayOfWeek: "",
			Interval:  60,
			Time:      "",
			Until:     "2023-02-11",
		},
		Tags:      []string{},
		UpdatedAt: dateparse.MustParse("2023-02-10T01:23:45.000Z"),
		User:      redash.User{},
		Version:   1,
		Visualizations: []redash.Visualization{
			{
				CreatedAt:   dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				Description: "description",
				ID:          1,
				Name:        "Table",
				Options:     map[string]any{},
				Query:       redash.Query{},
				Type:        "TABLE",
				UpdatedAt:   dateparse.MustParse("2023-02-10T01:23:45.000Z"),
			},
		},
	}, res)
}

func Test_GetQuery_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.GetQuery(context.Background(), 1)
	assert.ErrorContains(err, "GET api/queries/1 failed: HTTP status code not OK: 503\nerror")
}

func Test_GetQuery_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.GetQuery(context.Background(), 1)
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_CreateQuery_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{"data_source_id":1,"description":"description","name":"my-query","query":"select 1","schedule":{"interval":60,"time":null,"until":null,"day_of_week":null}}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"api_key": "api_key",
				"can_edit": true,
				"created_at": "2023-02-10T01:23:45.000Z",
				"data_source_id": 1,
				"description": "description",
				"id": 1,
				"is_archived": false,
				"is_draft": false,
				"is_favorite": false,
				"is_safe": true,
				"last_modified_by": {},
				"latest_query_data_id": 1,
				"name": "my-query",
				"options": {
					"parameters": []
				},
				"query": "select 1",
				"query_hash": "query_hash",
				"schedule": {
					"day_of_week": null,
					"interval": 60,
					"time": null,
					"until": "2023-02-11"
				},
				"tags": [],
				"updated_at": "2023-02-10T01:23:45.000Z",
				"user": {},
				"version": 1,
				"visualizations": [
					{
						"created_at": "2023-02-10T01:23:45.000Z",
						"description": "description",
						"id": 1,
						"name": "Table",
						"options": {},
						"type": "TABLE",
						"updated_at": "2023-02-10T01:23:45.000Z"
					}
				]
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.CreateQuery(context.Background(), &redash.CreateQueryInput{
		DataSourceID: 1,
		Description:  "description",
		Name:         "my-query",
		Query:        "select 1",
		Schedule: &redash.CreateQueryInputSchedule{
			Interval: 60,
		},
	})
	assert.NoError(err)
	assert.Equal(&redash.Query{
		APIKey:            "api_key",
		CanEdit:           true,
		CreatedAt:         dateparse.MustParse("2023-02-10T01:23:45.000Z"),
		DataSourceID:      1,
		Description:       "description",
		ID:                1,
		IsArchived:        false,
		IsDraft:           false,
		IsFavorite:        false,
		IsSafe:            true,
		LastModifiedBy:    &redash.User{},
		LastModifiedByID:  0,
		LatestQueryDataID: 1,
		Name:              "my-query",
		Options:           redash.QueryOptions{Parameters: []redash.QueryOptionsParameter{}},
		Query:             "select 1",
		QueryHash:         "query_hash",
		RetrievedAt:       time.Time{},
		Runtime:           0,
		Schedule: &redash.QueueSchedule{
			DayOfWeek: "",
			Interval:  60,
			Time:      "",
			Until:     "2023-02-11",
		},
		Tags:      []string{},
		UpdatedAt: dateparse.MustParse("2023-02-10T01:23:45.000Z"),
		User:      redash.User{},
		Version:   1,
		Visualizations: []redash.Visualization{
			{
				CreatedAt:   dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				Description: "description",
				ID:          1,
				Name:        "Table",
				Options:     map[string]any{},
				Query:       redash.Query{},
				Type:        "TABLE",
				UpdatedAt:   dateparse.MustParse("2023-02-10T01:23:45.000Z"),
			},
		},
	}, res)
}

func Test_CreateQuery_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.CreateQuery(context.Background(), &redash.CreateQueryInput{
		DataSourceID: 1,
		Description:  "description",
		Name:         "my-query",
		Query:        "select 1",
		Schedule: &redash.CreateQueryInputSchedule{
			Interval: 60,
		},
	})
	assert.ErrorContains(err, "POST api/queries failed: HTTP status code not OK: 503\nerror")
}

func Test_CreateQuery_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.CreateQuery(context.Background(), &redash.CreateQueryInput{
		DataSourceID: 1,
		Description:  "description",
		Name:         "my-query",
		Query:        "select 1",
		Schedule: &redash.CreateQueryInputSchedule{
			Interval: 60,
		},
	})
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_UpdateQuery_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{"data_source_id":1,"description":"description","name":"my-query","query":"select 1","schedule":{"interval":60,"time":null,"until":null,"day_of_week":null}}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"api_key": "api_key",
				"can_edit": true,
				"created_at": "2023-02-10T01:23:45.000Z",
				"data_source_id": 1,
				"description": "description",
				"id": 1,
				"is_archived": false,
				"is_draft": false,
				"is_favorite": false,
				"is_safe": true,
				"last_modified_by": {},
				"latest_query_data_id": 1,
				"name": "my-query",
				"options": {
					"parameters": []
				},
				"query": "select 1",
				"query_hash": "query_hash",
				"schedule": {
					"day_of_week": null,
					"interval": 60,
					"time": null,
					"until": "2023-02-11"
				},
				"tags": [],
				"updated_at": "2023-02-10T01:23:45.000Z",
				"user": {},
				"version": 1,
				"visualizations": [
					{
						"created_at": "2023-02-10T01:23:45.000Z",
						"description": "description",
						"id": 1,
						"name": "Table",
						"options": {},
						"type": "TABLE",
						"updated_at": "2023-02-10T01:23:45.000Z"
					}
				]
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.UpdateQuery(context.Background(), 1, &redash.UpdateQueryInput{
		DataSourceID: 1,
		Description:  "description",
		Name:         "my-query",
		Query:        "select 1",
		Schedule: &redash.UpdateQueryInputSchedule{
			Interval: 60,
		},
	})
	assert.NoError(err)
	assert.Equal(&redash.Query{
		APIKey:            "api_key",
		CanEdit:           true,
		CreatedAt:         dateparse.MustParse("2023-02-10T01:23:45.000Z"),
		DataSourceID:      1,
		Description:       "description",
		ID:                1,
		IsArchived:        false,
		IsDraft:           false,
		IsFavorite:        false,
		IsSafe:            true,
		LastModifiedBy:    &redash.User{},
		LastModifiedByID:  0,
		LatestQueryDataID: 1,
		Name:              "my-query",
		Options:           redash.QueryOptions{Parameters: []redash.QueryOptionsParameter{}},
		Query:             "select 1",
		QueryHash:         "query_hash",
		RetrievedAt:       time.Time{},
		Runtime:           0,
		Schedule: &redash.QueueSchedule{
			DayOfWeek: "",
			Interval:  60,
			Time:      "",
			Until:     "2023-02-11",
		},
		Tags:      []string{},
		UpdatedAt: dateparse.MustParse("2023-02-10T01:23:45.000Z"),
		User:      redash.User{},
		Version:   1,
		Visualizations: []redash.Visualization{
			{
				CreatedAt:   dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				Description: "description",
				ID:          1,
				Name:        "Table",
				Options:     map[string]any{},
				Query:       redash.Query{},
				Type:        "TABLE",
				UpdatedAt:   dateparse.MustParse("2023-02-10T01:23:45.000Z"),
			},
		},
	}, res)
}

func Test_UpdateQuery_OK_Publish(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{"data_source_id":1,"description":"description","name":"my-query","query":"select 1","schedule":{"interval":60,"time":null,"until":null,"day_of_week":null},"is_draft":false}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"api_key": "api_key",
				"can_edit": true,
				"created_at": "2023-02-10T01:23:45.000Z",
				"data_source_id": 1,
				"description": "description",
				"id": 1,
				"is_archived": false,
				"is_draft": false,
				"is_favorite": false,
				"is_safe": true,
				"last_modified_by": {},
				"latest_query_data_id": 1,
				"name": "my-query",
				"options": {
					"parameters": []
				},
				"query": "select 1",
				"query_hash": "query_hash",
				"schedule": {
					"day_of_week": null,
					"interval": 60,
					"time": null,
					"until": "2023-02-11"
				},
				"tags": [],
				"updated_at": "2023-02-10T01:23:45.000Z",
				"user": {},
				"version": 1,
				"visualizations": [
					{
						"created_at": "2023-02-10T01:23:45.000Z",
						"description": "description",
						"id": 1,
						"name": "Table",
						"options": {},
						"type": "TABLE",
						"updated_at": "2023-02-10T01:23:45.000Z"
					}
				]
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	isDraft := false

	res, err := client.UpdateQuery(context.Background(), 1, &redash.UpdateQueryInput{
		DataSourceID: 1,
		Description:  "description",
		Name:         "my-query",
		Query:        "select 1",
		Schedule: &redash.UpdateQueryInputSchedule{
			Interval: 60,
		},
		IsDraft: &isDraft,
	})
	assert.NoError(err)
	assert.Equal(&redash.Query{
		APIKey:            "api_key",
		CanEdit:           true,
		CreatedAt:         dateparse.MustParse("2023-02-10T01:23:45.000Z"),
		DataSourceID:      1,
		Description:       "description",
		ID:                1,
		IsArchived:        false,
		IsDraft:           false,
		IsFavorite:        false,
		IsSafe:            true,
		LastModifiedBy:    &redash.User{},
		LastModifiedByID:  0,
		LatestQueryDataID: 1,
		Name:              "my-query",
		Options:           redash.QueryOptions{Parameters: []redash.QueryOptionsParameter{}},
		Query:             "select 1",
		QueryHash:         "query_hash",
		RetrievedAt:       time.Time{},
		Runtime:           0,
		Schedule: &redash.QueueSchedule{
			DayOfWeek: "",
			Interval:  60,
			Time:      "",
			Until:     "2023-02-11",
		},
		Tags:      []string{},
		UpdatedAt: dateparse.MustParse("2023-02-10T01:23:45.000Z"),
		User:      redash.User{},
		Version:   1,
		Visualizations: []redash.Visualization{
			{
				CreatedAt:   dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				Description: "description",
				ID:          1,
				Name:        "Table",
				Options:     map[string]any{},
				Query:       redash.Query{},
				Type:        "TABLE",
				UpdatedAt:   dateparse.MustParse("2023-02-10T01:23:45.000Z"),
			},
		},
	}, res)
}

func Test_UpdateQuery_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.UpdateQuery(context.Background(), 1, &redash.UpdateQueryInput{
		DataSourceID: 1,
		Description:  "description",
		Name:         "my-query",
		Query:        "select 1",
		Schedule: &redash.UpdateQueryInputSchedule{
			Interval: 60,
		},
	})
	assert.ErrorContains(err, "POST api/queries/1 failed: HTTP status code not OK: 503\nerror")
}

func Test_UpdateQuery_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.UpdateQuery(context.Background(), 1, &redash.UpdateQueryInput{
		DataSourceID: 1,
		Description:  "description",
		Name:         "my-query",
		Query:        "select 1",
		Schedule: &redash.UpdateQueryInputSchedule{
			Interval: 60,
		},
	})
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_PublishQuery_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{"is_draft":false}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, ``), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	err := client.PublishQuery(context.Background(), 1)
	assert.NoError(err)
}

func Test_PublishQuery_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	err := client.PublishQuery(context.Background(), 1)
	assert.ErrorContains(err, "POST api/queries/1 failed: HTTP status code not OK: 503\nerror")
}

func Test_UnpublishQuery_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{"is_draft":true}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, ``), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	err := client.UnpublishQuery(context.Background(), 1)
	assert.NoError(err)
}

func Test_UnpublishQuery_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	err := client.UnpublishQuery(context.Background(), 1)
	assert.ErrorContains(err, "POST api/queries/1 failed: HTTP status code not OK: 503\nerror")
}

func Test_ArchiveQuery_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodDelete, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, ``), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	err := client.ArchiveQuery(context.Background(), 1)
	assert.NoError(err)
}

func Test_ArchiveQuery_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodDelete, "https://redash.example.com/api/queries/1", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	err := client.ArchiveQuery(context.Background(), 1)
	assert.ErrorContains(err, "DELETE api/queries/1 failed: HTTP status code not OK: 503\nerror")
}

func Test_CreateFavoriteQuery_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/favorite", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, ``), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	err := client.CreateFavoriteQuery(context.Background(), 1)
	assert.NoError(err)
}

func Test_CreateFavoriteQuery_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/favorite", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusInternalServerError, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	err := client.CreateFavoriteQuery(context.Background(), 1)
	assert.ErrorContains(err, "POST api/queries/1/favorite failed: HTTP status code not OK: 500\nerror")
}

func Test_ForkQuery_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/fork", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"api_key": "api_key",
				"can_edit": true,
				"created_at": "2023-02-10T01:23:45.000Z",
				"data_source_id": 1,
				"description": "description",
				"id": 1,
				"is_archived": false,
				"is_draft": false,
				"is_favorite": false,
				"is_safe": true,
				"last_modified_by": {},
				"latest_query_data_id": 1,
				"name": "my-query",
				"options": {
					"parameters": []
				},
				"query": "select 1",
				"query_hash": "query_hash",
				"schedule": {
					"day_of_week": null,
					"interval": 60,
					"time": null,
					"until": "2023-02-11"
				},
				"tags": [],
				"updated_at": "2023-02-10T01:23:45.000Z",
				"user": {},
				"version": 1,
				"visualizations": [
					{
						"created_at": "2023-02-10T01:23:45.000Z",
						"description": "description",
						"id": 1,
						"name": "Table",
						"options": {},
						"type": "TABLE",
						"updated_at": "2023-02-10T01:23:45.000Z"
					}
				]
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.ForkQuery(context.Background(), 1)
	assert.NoError(err)
	assert.Equal(&redash.Query{
		APIKey:            "api_key",
		CanEdit:           true,
		CreatedAt:         dateparse.MustParse("2023-02-10T01:23:45.000Z"),
		DataSourceID:      1,
		Description:       "description",
		ID:                1,
		IsArchived:        false,
		IsDraft:           false,
		IsFavorite:        false,
		IsSafe:            true,
		LastModifiedBy:    &redash.User{},
		LastModifiedByID:  0,
		LatestQueryDataID: 1,
		Name:              "my-query",
		Options:           redash.QueryOptions{Parameters: []redash.QueryOptionsParameter{}},
		Query:             "select 1",
		QueryHash:         "query_hash",
		RetrievedAt:       time.Time{},
		Runtime:           0,
		Schedule: &redash.QueueSchedule{
			DayOfWeek: "",
			Interval:  60,
			Time:      "",
			Until:     "2023-02-11",
		},
		Tags:      []string{},
		UpdatedAt: dateparse.MustParse("2023-02-10T01:23:45.000Z"),
		User:      redash.User{},
		Version:   1,
		Visualizations: []redash.Visualization{
			{
				CreatedAt:   dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				Description: "description",
				ID:          1,
				Name:        "Table",
				Options:     map[string]any{},
				Query:       redash.Query{},
				Type:        "TABLE",
				UpdatedAt:   dateparse.MustParse("2023-02-10T01:23:45.000Z"),
			},
		},
	}, res)
}

func Test_ForkQuery_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/fork", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.ForkQuery(context.Background(), 1)
	assert.ErrorContains(err, "POST api/queries/1/fork failed: HTTP status code not OK: 503\nerror")
}

func Test_ForkQuery_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/fork", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.ForkQuery(context.Background(), 1)
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_GetQueryResultsJSON_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `{"foo":"bar"}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResultsJSON(context.Background(), 1, &buf)
	assert.NoError(err)
	assert.Equal(`{"foo":"bar"}`, buf.String())
}

func Test_GetQueryResultsJSON_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResultsJSON(context.Background(), 1, &buf)
	assert.ErrorContains(err, "GET api/queries/1/results.json failed: HTTP status code not OK: 503\nerror")
}

func Test_GetQueryResultsJSON_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResultsJSON(context.Background(), 1, &buf)
	assert.ErrorContains(err, "IO error")
}

func Test_JsonToGetQueryResultsOutput_OK(t *testing.T) {
	assert := assert.New(t)
	queryResult := `{"query_result": {"id": 73, "query_hash": "9c12398e42fb85a93a3b1c726f0844b4", "query": "select now()", "data": {"columns": [{"name": "now", "friendly_name": "now", "type": "datetime"}], "rows": [{"now": "2024-06-09T06:14:32.922Z"}]}, "data_source_id": 93, "runtime": 0.021225452423095703, "retrieved_at": "2024-06-09T06:14:32.930Z"}}`
	out, err := redash.JsonToGetQueryResultsOutput([]byte(queryResult))
	assert.NoError(err)
	assert.Equal(
		&redash.GetQueryResultsOutput{
			QueryResult: redash.GetQueryResultsOutputQueryResult{
				ID:        73,
				QueryHash: "9c12398e42fb85a93a3b1c726f0844b4",
				Query:     "select now()",
				Data: redash.GetQueryResultsOutputQueryResultData{
					Columns: []redash.GetQueryResultsOutputQueryResultDataColumn{
						{Name: "now", FriendlyName: "now", Type: "datetime"},
					},
					Rows: []map[string]interface{}{
						{"now": "2024-06-09T06:14:32.922Z"},
					},
				},
				DataSourceID: 93,
				Runtime:      0.021225452423095703,
				RetrievedAt:  time.Date(2024, time.June, 9, 6, 14, 32, 930000000, time.UTC),
			},
		},
		out,
	)
}

func Test_JsonToGetQueryResultsOutput_Err(t *testing.T) {
	assert := assert.New(t)
	_, err := redash.JsonToGetQueryResultsOutput([]byte(`}{`))
	assert.ErrorContains(err, "invalid character '}' looking for beginning of value")
}

func Test_GetQueryResultsStruct_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `{"query_result": {"id": 73, "query_hash": "9c12398e42fb85a93a3b1c726f0844b4", "query": "select now()", "data": {"columns": [{"name": "now", "friendly_name": "now", "type": "datetime"}], "rows": [{"now": "2024-06-09T06:14:32.922Z"}]}, "data_source_id": 93, "runtime": 0.021225452423095703, "retrieved_at": "2024-06-09T06:14:32.930Z"}}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	out, err := client.GetQueryResultsStruct(context.Background(), 1)
	assert.NoError(err)
	assert.Equal(
		&redash.GetQueryResultsOutput{
			QueryResult: redash.GetQueryResultsOutputQueryResult{
				ID:        73,
				QueryHash: "9c12398e42fb85a93a3b1c726f0844b4",
				Query:     "select now()",
				Data: redash.GetQueryResultsOutputQueryResultData{
					Columns: []redash.GetQueryResultsOutputQueryResultDataColumn{
						{Name: "now", FriendlyName: "now", Type: "datetime"},
					},
					Rows: []map[string]interface{}{
						{"now": "2024-06-09T06:14:32.922Z"},
					},
				},
				DataSourceID: 93,
				Runtime:      0.021225452423095703,
				RetrievedAt:  time.Date(2024, time.June, 9, 6, 14, 32, 930000000, time.UTC),
			},
		},
		out,
	)
}

func Test_GetQueryResultsStruct_Err(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `}{`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.GetQueryResultsStruct(context.Background(), 1)
	assert.ErrorContains(err, "invalid character '}' looking for beginning of value")
}

func Test_GetQueryResultsStruct_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.GetQueryResultsStruct(context.Background(), 1)
	assert.ErrorContains(err, "GET api/queries/1/results.json failed: HTTP status code not OK: 503\nerror")
}

func Test_GetQueryResultsStruct_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.GetQueryResultsStruct(context.Background(), 1)
	assert.ErrorContains(err, "IO error")
}

func Test_GetQueryResultsCSV_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.csv", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `foo,bar`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResultsCSV(context.Background(), 1, &buf)
	assert.NoError(err)
	assert.Equal(`foo,bar`, buf.String())
}

func Test_GetQueryResultsCSV_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.csv", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResultsCSV(context.Background(), 1, &buf)
	assert.ErrorContains(err, "GET api/queries/1/results.csv failed: HTTP status code not OK: 503\nerror")
}

func Test_GetQueryResultsCSV_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.csv", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResultsCSV(context.Background(), 1, &buf)
	assert.ErrorContains(err, "IO error")
}

func Test_GetQueryResults_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `{"foo":"bar"}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResults(context.Background(), 1, "json", &buf)
	assert.NoError(err)
	assert.Equal(`{"foo":"bar"}`, buf.String())
}

func Test_GetQueryResults_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResults(context.Background(), 1, "json", &buf)
	assert.ErrorContains(err, "GET api/queries/1/results.json failed: HTTP status code not OK: 503\nerror")
}

func Test_GetQueryResults_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResults(context.Background(), 1, "json", &buf)
	assert.ErrorContains(err, "IO error")
}

func Test_GetQueryResults_OK_WithNil(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `{"foo":"bar"}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	err := client.GetQueryResults(context.Background(), 1, "json", nil)
	assert.ErrorContains(err, "out(io.Writer) is nil")
}

func Test_GetQueryResultByID_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/query_results/1", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `{"foo":"bar"}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResultByID(context.Background(), 1, "", &buf)
	assert.NoError(err)
	assert.Equal(`{"foo":"bar"}`, buf.String())
}

func Test_GetQueryResultByID_OK_WithExt(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/query_results/1.xlsx", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `{"foo":"bar"}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResultByID(context.Background(), 1, "xlsx", &buf)
	assert.NoError(err)
	assert.Equal(`{"foo":"bar"}`, buf.String())
}

func Test_GetQueryResultByID_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/query_results/1.json", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResultByID(context.Background(), 1, "json", &buf)
	assert.ErrorContains(err, "GET api/query_results/1.json failed: HTTP status code not OK: 503\nerror")
}

func Test_GetQueryResultByID_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/query_results/1", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	err := client.GetQueryResultByID(context.Background(), 1, "", &buf)
	assert.ErrorContains(err, "IO error")
}

func Test_GetQueryResultByID_ArgErr(t *testing.T) {
	assert := assert.New(t)
	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	err := client.GetQueryResultByID(context.Background(), 1, "", nil)
	assert.ErrorContains(err, "out(io.Writer) is nil")
}

func Test_ExecQueryJSON_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `{"foo":"bar"}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	jobId, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, &buf)
	assert.NoError(err)
	assert.Equal(`{"foo":"bar"}`, buf.String())
	assert.Empty(jobId)
}

func Test_ExecQueryJSON_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	_, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, &buf)
	assert.ErrorContains(err, "POST api/queries/1/results failed: HTTP status code not OK: 503\nerror")
}

func Test_ExecQueryJSON_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	_, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, &buf)
	assert.ErrorContains(err, "IO error")
}

func Test_ExecQueryJSON_OK_WithNil(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `{"foo":"bar"}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	jobId, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, nil)
	assert.NoError(err)
	assert.Empty(jobId)
}

func Test_ExecQueryJSON_OK_WithParams(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{"parameters":{"date_param":"2020-01-01","date_range_param":{"end":"2020-12-31","start":"2020-01-01"},"number_param":100},"apply_auto_limit":true,"max_age":1800}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `{"foo":"bar"}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	jobId, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{
		Parameters: map[string]any{
			"number_param": 100,
			"date_param":   "2020-01-01",
			"date_range_param": map[string]string{
				"start": "2020-01-01",
				"end":   "2020-12-31",
			},
		},
		ApplyAutoLimit: true,
		MaxAge:         1800,
	}, &buf)
	assert.NoError(err)
	assert.Equal(`{"foo":"bar"}`, buf.String())
	assert.Empty(jobId)
}

func Test_ExecQueryJSON_OK_WithMaxAgeZero(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{"parameters":{"date_param":"2020-01-01","date_range_param":{"end":"2020-12-31","start":"2020-01-01"},"number_param":100},"apply_auto_limit":true,"max_age":0}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `{"foo":"bar"}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	jobId, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{
		Parameters: map[string]any{
			"number_param": 100,
			"date_param":   "2020-01-01",
			"date_range_param": map[string]string{
				"start": "2020-01-01",
				"end":   "2020-12-31",
			},
		},
		ApplyAutoLimit:        true,
		WithoutOmittingMaxAge: true,
	}, &buf)
	assert.NoError(err)
	assert.Equal(`{"foo":"bar"}`, buf.String())
	assert.Empty(jobId)
}

func Test_ExecQueryJSON_ReturnJob(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `{"job": {"status": 1, "error": "", "id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51", "query_result_id": null,	"status": 1, "updated_at": 0}}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	job, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, nil)
	assert.NoError(err)
	assert.Equal(&redash.JobResponse{
		Job: redash.Job{
			Error:         "",
			ID:            "623b290a-7fd9-4ea6-a2a6-96f9c9101f51",
			QueryResultID: 0,
			Status:        1,
			UpdatedAt:     float64(0),
		},
	}, job)
}

func Test_WaitQueryJSON_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `{"job": {"status": 1, "error": "", "id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51", "query_result_id": null,	"status": 1, "updated_at": 0}}`), nil
	})

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/jobs/623b290a-7fd9-4ea6-a2a6-96f9c9101f51", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"job": {
					"error": "",
					"id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51",
					"query_result_id": 1,
					"status": 3,
					"updated_at": 0
				}
			}
		`), nil
	})

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `{"foo":"bar"}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	job, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, &buf)
	assert.NoError(err)
	err = client.WaitQueryJSON(context.Background(), 1, job, nil, &buf)
	assert.NoError(err)
	assert.Equal(`{"foo":"bar"}`, buf.String())
}

func Test_WaitQueryJSON_Err_GetJob(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `{"job": {"status": 1, "error": "", "id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51", "query_result_id": null,	"status": 1, "updated_at": 0}}`), nil
	})

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/jobs/623b290a-7fd9-4ea6-a2a6-96f9c9101f51", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	job, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, &buf)
	assert.NoError(err)
	err = client.WaitQueryJSON(context.Background(), 1, job, nil, &buf)
	assert.ErrorContains(err, "GET api/jobs/623b290a-7fd9-4ea6-a2a6-96f9c9101f51 failed: HTTP status code not OK: 503\nerror")
}

func Test_WaitQueryJSON_Err_GetQueryResultsJSON(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `{"job": {"status": 1, "error": "", "id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51", "query_result_id": null,	"status": 1, "updated_at": 0}}`), nil
	})

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/jobs/623b290a-7fd9-4ea6-a2a6-96f9c9101f51", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"job": {
					"error": "",
					"id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51",
					"query_result_id": 1,
					"status": 3,
					"updated_at": 0
				}
			}
		`), nil
	})

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	job, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, &buf)
	assert.NoError(err)
	err = client.WaitQueryJSON(context.Background(), 1, job, nil, &buf)
	assert.ErrorContains(err, "GET api/queries/1/results.json failed: HTTP status code not OK: 503\nerror")
}

func Test_WaitQueryStruct_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `{"job": {"status": 1, "error": "", "id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51", "query_result_id": null,	"status": 1, "updated_at": 0}}`), nil
	})

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/jobs/623b290a-7fd9-4ea6-a2a6-96f9c9101f51", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"job": {
					"error": "",
					"id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51",
					"query_result_id": 1,
					"status": 3,
					"updated_at": 0
				}
			}
		`), nil
	})

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `{"query_result": {"id": 73, "query_hash": "9c12398e42fb85a93a3b1c726f0844b4", "query": "select now()", "data": {"columns": [{"name": "now", "friendly_name": "now", "type": "datetime"}], "rows": [{"now": "2024-06-09T06:14:32.922Z"}]}, "data_source_id": 93, "runtime": 0.021225452423095703, "retrieved_at": "2024-06-09T06:14:32.930Z"}}`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	job, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, &buf)
	assert.NoError(err)
	out, err := client.WaitQueryStruct(context.Background(), 1, job, nil, &buf)
	assert.NoError(err)
	assert.Equal(&redash.GetQueryResultsOutput{
		QueryResult: redash.GetQueryResultsOutputQueryResult{
			ID:        73,
			QueryHash: "9c12398e42fb85a93a3b1c726f0844b4",
			Query:     "select now()",
			Data: redash.GetQueryResultsOutputQueryResultData{
				Columns: []redash.GetQueryResultsOutputQueryResultDataColumn{
					{Name: "now", FriendlyName: "now", Type: "datetime"},
				},
				Rows: []map[string]interface{}{
					{"now": "2024-06-09T06:14:32.922Z"},
				},
			},
			DataSourceID: 93,
			Runtime:      0.021225452423095703,
			RetrievedAt:  time.Date(2024, time.June, 9, 6, 14, 32, 930000000, time.UTC),
		},
	}, out)
}

func Test_WaitQueryStruct_Err_WaitQueryJSON(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `{"job": {"status": 1, "error": "", "id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51", "query_result_id": null,	"status": 1, "updated_at": 0}}`), nil
	})

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/jobs/623b290a-7fd9-4ea6-a2a6-96f9c9101f51", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	job, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, &buf)
	assert.NoError(err)
	_, err = client.WaitQueryStruct(context.Background(), 1, job, nil, &buf)
	assert.ErrorContains(err, "GET api/jobs/623b290a-7fd9-4ea6-a2a6-96f9c9101f51 failed: HTTP status code not OK: 503\nerror")
}

func Test_WaitQueryStruct_JsonToGetQueryResultsOutput(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/results", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `{"job": {"status": 1, "error": "", "id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51", "query_result_id": null,	"status": 1, "updated_at": 0}}`), nil
	})

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/jobs/623b290a-7fd9-4ea6-a2a6-96f9c9101f51", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"job": {
					"error": "",
					"id": "623b290a-7fd9-4ea6-a2a6-96f9c9101f51",
					"query_result_id": 1,
					"status": 3,
					"updated_at": 0
				}
			}
		`), nil
	})

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/1/results.json", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `}{`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	var buf bytes.Buffer
	job, err := client.ExecQueryJSON(context.Background(), 1, &redash.ExecQueryJSONInput{}, &buf)
	assert.NoError(err)
	_, err = client.WaitQueryStruct(context.Background(), 1, job, nil, &buf)
	assert.ErrorContains(err, "invalid character '}' looking for beginning of value")
}

func Test_GetQueryTags_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/tags", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"tags": [
					{
						"count": 1,
						"name": "my-tag"
					}
				]
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.GetQueryTags(context.Background())
	assert.NoError(err)
	assert.Equal(&redash.QueryTags{
		Tags: []redash.QueryTagsTag{
			{
				Name:  "my-tag",
				Count: 1,
			},
		},
	}, res)
}

func Test_GetQueryTags_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/tags", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.GetQueryTags(context.Background())
	assert.ErrorContains(err, "GET api/queries/tags failed: HTTP status code not OK: 503\nerror")
}

func Test_GetQueryTags_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/tags", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.GetQueryTags(context.Background())
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_RefreshQuery_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/refresh", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"job": {
					"error": "",
					"id": "baaf5b97-6419-4db3-a60c-ef8b4e290376",
					"query_result_id": null,
					"result": null,
					"status": 1,
					"updated_at": 0
				}
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	job, err := client.RefreshQuery(context.Background(), 1, nil)
	assert.NoError(err)
	assert.Equal(&redash.JobResponse{
		Job: redash.Job{
			Error:         "",
			ID:            "baaf5b97-6419-4db3-a60c-ef8b4e290376",
			QueryResultID: 0,
			Status:        1,
			UpdatedAt:     float64(0),
		},
	}, job)
}

func Test_RefreshQuery_OK_WithInput(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/refresh", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{"apply_auto_limit":true}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"job": {
					"error": "",
					"id": "baaf5b97-6419-4db3-a60c-ef8b4e290376",
					"query_result_id": null,
					"result": null,
					"status": 1,
					"updated_at": 0
				}
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	job, err := client.RefreshQuery(context.Background(), 1, &redash.RefreshQueryInput{
		ApplyAutoLimit: true,
	})
	assert.NoError(err)
	assert.Equal(&redash.JobResponse{
		Job: redash.Job{
			Error:         "",
			ID:            "baaf5b97-6419-4db3-a60c-ef8b4e290376",
			QueryResultID: 0,
			Status:        1,
			UpdatedAt:     float64(0),
		},
	}, job)
}

func Test_RefreshQuery_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/refresh", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.RefreshQuery(context.Background(), 1, nil)
	assert.ErrorContains(err, "POST api/queries/1/refresh failed: HTTP status code not OK: 503\nerror")
}

func Test_RefreshQuery_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/1/refresh", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.RefreshQuery(context.Background(), 1, nil)
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_SearchQueries_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/search", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		assert.Equal("q=my-query", req.URL.Query().Encode())
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"count": 1,
				"page": 1,
				"page_size": 25,
				"results": [
					{
						"api_key": "api_key",
						"created_at": "2023-02-10T01:23:45.000Z",
						"data_source_id": 1,
						"description": "description",
						"id": 1,
						"is_archived": false,
						"is_draft": false,
						"is_favorite": false,
						"is_safe": true,
						"last_modified_by_id": 1,
						"latest_query_data_id": 1,
						"name": "my-query",
						"options": {
							"parameters": []
						},
						"query": "select 1",
						"query_hash": "query_hash",
						"retrieved_at": "2023-02-10T01:23:45.000Z",
						"runtime": 0.1,
						"schedule": {
							"day_of_week": null,
							"interval": 60,
							"time": null,
							"until": "2023-02-11"
						},
						"tags": [],
						"updated_at": "2023-02-10T01:23:45.000Z",
						"user": {},
						"version": 1
					}
				]
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.SearchQueries(context.Background(), &redash.SearchQueriesInput{
		Q: "my-query",
	})
	assert.NoError(err)
	assert.Equal(&redash.QueryPage{
		Count:    1,
		Page:     1,
		PageSize: 25,
		Results: []redash.Query{
			{
				APIKey:            "api_key",
				CanEdit:           false,
				CreatedAt:         dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				DataSourceID:      1,
				Description:       "description",
				ID:                1,
				IsArchived:        false,
				IsDraft:           false,
				IsFavorite:        false,
				IsSafe:            true,
				LastModifiedBy:    nil,
				LastModifiedByID:  1,
				LatestQueryDataID: 1,
				Name:              "my-query",
				Options:           redash.QueryOptions{Parameters: []redash.QueryOptionsParameter{}},
				Query:             "select 1",
				QueryHash:         "query_hash",
				RetrievedAt:       dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				Runtime:           0.1,
				Schedule: &redash.QueueSchedule{
					DayOfWeek: "",
					Interval:  60,
					Time:      "",
					Until:     "2023-02-11",
				},
				Tags:           []string{},
				UpdatedAt:      dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				User:           redash.User{},
				Version:        1,
				Visualizations: nil,
			},
		},
	}, res)
}

func Test_SearchQueries_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/search", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.SearchQueries(context.Background(), &redash.SearchQueriesInput{
		Q: "my-query",
	})
	assert.ErrorContains(err, "GET api/queries/search failed: HTTP status code not OK: 503")
}

func Test_SearchQueries_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/search", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.SearchQueries(context.Background(), &redash.SearchQueriesInput{
		Q: "my-query",
	})
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_ListMyQueries_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/my", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		assert.Equal("page=1&page_size=25&q=my-query", req.URL.Query().Encode())
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"count": 1,
				"page": 1,
				"page_size": 25,
				"results": [
					{
						"api_key": "api_key",
						"created_at": "2023-02-10T01:23:45.000Z",
						"data_source_id": 1,
						"description": "description",
						"id": 1,
						"is_archived": false,
						"is_draft": false,
						"is_favorite": false,
						"is_safe": true,
						"last_modified_by_id": 1,
						"latest_query_data_id": 1,
						"name": "my-query",
						"options": {
							"parameters": []
						},
						"query": "select 1",
						"query_hash": "query_hash",
						"retrieved_at": "2023-02-10T01:23:45.000Z",
						"runtime": 0.1,
						"schedule": {
							"day_of_week": null,
							"interval": 60,
							"time": null,
							"until": "2023-02-11"
						},
						"tags": [],
						"updated_at": "2023-02-10T01:23:45.000Z",
						"user": {},
						"version": 1
					}
				]
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.ListMyQueries(context.Background(), &redash.ListMyQueriesInput{
		Page:     1,
		PageSize: 25,
		Q:        "my-query",
	})
	assert.NoError(err)
	assert.Equal(&redash.QueryPage{
		Count:    1,
		Page:     1,
		PageSize: 25,
		Results: []redash.Query{
			{
				APIKey:            "api_key",
				CanEdit:           false,
				CreatedAt:         dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				DataSourceID:      1,
				Description:       "description",
				ID:                1,
				IsArchived:        false,
				IsDraft:           false,
				IsFavorite:        false,
				IsSafe:            true,
				LastModifiedBy:    nil,
				LastModifiedByID:  1,
				LatestQueryDataID: 1,
				Name:              "my-query",
				Options:           redash.QueryOptions{Parameters: []redash.QueryOptionsParameter{}},
				Query:             "select 1",
				QueryHash:         "query_hash",
				RetrievedAt:       dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				Runtime:           0.1,
				Schedule: &redash.QueueSchedule{
					DayOfWeek: "",
					Interval:  60,
					Time:      "",
					Until:     "2023-02-11",
				},
				Tags:           []string{},
				UpdatedAt:      dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				User:           redash.User{},
				Version:        1,
				Visualizations: nil,
			},
		},
	}, res)
}

func Test_ListMyQueries_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/my", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.ListMyQueries(context.Background(), &redash.ListMyQueriesInput{
		Page:     1,
		PageSize: 25,
		Q:        "my-query",
	})
	assert.ErrorContains(err, "GET api/queries/my failed: HTTP status code not OK: 503\nerror")
}

func Test_ListMyQueries_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/my", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.ListMyQueries(context.Background(), &redash.ListMyQueriesInput{
		Page:     1,
		PageSize: 25,
		Q:        "my-query",
	})
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_ListFavoriteQueries_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/favorites", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		assert.Equal("page=1&page_size=25&q=my-query", req.URL.Query().Encode())
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"count": 1,
				"page": 1,
				"page_size": 25,
				"results": [
					{
						"api_key": "api_key",
						"created_at": "2023-02-10T01:23:45.000Z",
						"data_source_id": 1,
						"description": "description",
						"id": 1,
						"is_archived": false,
						"is_draft": false,
						"is_favorite": false,
						"is_safe": true,
						"last_modified_by_id": 1,
						"latest_query_data_id": 1,
						"name": "my-query",
						"options": {
							"parameters": []
						},
						"query": "select 1",
						"query_hash": "query_hash",
						"retrieved_at": "2023-02-10T01:23:45.000Z",
						"runtime": 0.1,
						"schedule": {
							"day_of_week": null,
							"interval": 60,
							"time": null,
							"until": "2023-02-11"
						},
						"tags": [],
						"updated_at": "2023-02-10T01:23:45.000Z",
						"user": {},
						"version": 1
					}
				]
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.ListFavoriteQueries(context.Background(), &redash.ListFavoriteQueriesInput{
		Page:     1,
		PageSize: 25,
		Q:        "my-query",
	})
	assert.NoError(err)
	assert.Equal(&redash.QueryPage{
		Count:    1,
		Page:     1,
		PageSize: 25,
		Results: []redash.Query{
			{
				APIKey:            "api_key",
				CanEdit:           false,
				CreatedAt:         dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				DataSourceID:      1,
				Description:       "description",
				ID:                1,
				IsArchived:        false,
				IsDraft:           false,
				IsFavorite:        false,
				IsSafe:            true,
				LastModifiedBy:    nil,
				LastModifiedByID:  1,
				LatestQueryDataID: 1,
				Name:              "my-query",
				Options:           redash.QueryOptions{Parameters: []redash.QueryOptionsParameter{}},
				Query:             "select 1",
				QueryHash:         "query_hash",
				RetrievedAt:       dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				Runtime:           0.1,
				Schedule: &redash.QueueSchedule{
					DayOfWeek: "",
					Interval:  60,
					Time:      "",
					Until:     "2023-02-11",
				},
				Tags:           []string{},
				UpdatedAt:      dateparse.MustParse("2023-02-10T01:23:45.000Z"),
				User:           redash.User{},
				Version:        1,
				Visualizations: nil,
			},
		},
	}, res)
}

func Test_ListFavoriteQueries_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/favorites", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.ListFavoriteQueries(context.Background(), &redash.ListFavoriteQueriesInput{
		Page:     1,
		PageSize: 25,
		Q:        "my-query",
	})
	assert.ErrorContains(err, "GET api/queries/favorites failed: HTTP status code not OK: 503\nerror")
}

func Test_ListFavoriteQueries_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/favorites", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.ListFavoriteQueries(context.Background(), &redash.ListFavoriteQueriesInput{
		Page:     1,
		PageSize: 25,
		Q:        "my-query",
	})
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_FormatQuery_OK(t *testing.T) {
	assert := assert.New(t)
	require := require.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/format", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		require.NotNil(req.Body)
		body, _ := io.ReadAll(req.Body)
		assert.Equal(`{"query":"select 1 from dual"}`, string(body))
		return httpmock.NewStringResponse(http.StatusOK, `
			{
				"query": "SELECT 1\nFROM dual"
			}
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.FormatQuery(context.Background(), "select 1 from dual")
	assert.NoError(err)
	assert.Equal(&redash.FormatQueryOutput{
		Query: "SELECT 1\nFROM dual",
	}, res)
}

func Test_FormatQuery_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/format", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.FormatQuery(context.Background(), "select 1 from dual")
	assert.ErrorContains(err, "POST api/queries/format failed: HTTP status code not OK: 503\nerror")
}

func Test_FormatQuery_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodPost, "https://redash.example.com/api/queries/format", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.FormatQuery(context.Background(), "select 1 from dual")
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_ListRecentQueries_OK(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/recent", func(req *http.Request) (*http.Response, error) {
		assert.Equal(
			http.Header(
				http.Header{
					"Authorization": []string{"Key " + testRedashAPIKey},
					"Content-Type":  []string{"application/json"},
					"User-Agent":    []string{"redash-go"},
				},
			),
			req.Header,
		)
		return httpmock.NewStringResponse(http.StatusOK, `
			[
				{
					"api_key": "api_key",
					"created_at": "2023-02-10T01:23:45.000Z",
					"data_source_id": 1,
					"description": "description",
					"id": 1,
					"is_archived": false,
					"is_draft": false,
					"is_favorite": false,
					"is_safe": true,
					"last_modified_by_id": 1,
					"latest_query_data_id": 1,
					"name": "my-query",
					"options": {
						"parameters": []
					},
					"query": "select 1",
					"query_hash": "query_hash",
					"retrieved_at": "2023-02-10T01:23:45.000Z",
					"runtime": 0.1,
					"schedule": {
						"day_of_week": null,
						"interval": 60,
						"time": null,
						"until": "2023-02-11"
					},
					"tags": [],
					"updated_at": "2023-02-10T01:23:45.000Z",
					"user": {},
					"version": 1
				}
			]
		`), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	res, err := client.ListRecentQueries(context.Background())
	assert.NoError(err)
	assert.Equal([]redash.Query{
		{
			APIKey:            "api_key",
			CanEdit:           false,
			CreatedAt:         dateparse.MustParse("2023-02-10T01:23:45.000Z"),
			DataSourceID:      1,
			Description:       "description",
			ID:                1,
			IsArchived:        false,
			IsDraft:           false,
			IsFavorite:        false,
			IsSafe:            true,
			LastModifiedBy:    nil,
			LastModifiedByID:  1,
			LatestQueryDataID: 1,
			Name:              "my-query",
			Options:           redash.QueryOptions{Parameters: []redash.QueryOptionsParameter{}},
			Query:             "select 1",
			QueryHash:         "query_hash",
			RetrievedAt:       dateparse.MustParse("2023-02-10T01:23:45.000Z"),
			Runtime:           0.1,
			Schedule: &redash.QueueSchedule{
				DayOfWeek: "",
				Interval:  60,
				Time:      "",
				Until:     "2023-02-11",
			},
			Tags:           []string{},
			UpdatedAt:      dateparse.MustParse("2023-02-10T01:23:45.000Z"),
			User:           redash.User{},
			Version:        1,
			Visualizations: nil,
		},
	}, res)
}

func Test_ListRecentQueries_Err_5xx(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/recent", func(req *http.Request) (*http.Response, error) {
		return httpmock.NewStringResponse(http.StatusServiceUnavailable, "error"), nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.ListRecentQueries(context.Background())
	assert.ErrorContains(err, "GET api/queries/recent failed: HTTP status code not OK: 503\nerror")
}

func Test_ListRecentQueries_IOErr(t *testing.T) {
	assert := assert.New(t)
	httpmock.Activate()
	defer httpmock.DeactivateAndReset()

	httpmock.RegisterResponder(http.MethodGet, "https://redash.example.com/api/queries/recent", func(req *http.Request) (*http.Response, error) {
		return testIOErrResp, nil
	})

	client, _ := redash.NewClient("https://redash.example.com", testRedashAPIKey)
	_, err := client.ListRecentQueries(context.Background())
	assert.ErrorContains(err, "Read response body failed: IO error")
}

func Test_Query_Acc(t *testing.T) {
	if !testAcc {
		t.Skip()
	}

	assert := assert.New(t)
	require := require.New(t)
	client, _ := redash.NewClient(testRedashEndpoint, testRedashAPIKey)
	ds, err := client.CreateDataSource(context.Background(), &redash.CreateDataSourceInput{
		Name: "test-postgres-1",
		Type: "pg",
		Options: map[string]any{
			"dbname": "postgres",
			"host":   "postgres",
			"port":   5432,
			"user":   "postgres",
		},
	})
	require.NoError(err)

	defer func() {
		client.DeleteDataSource(context.Background(), ds.ID) //nolint:errcheck
	}()

	_, err = client.ListQueries(context.Background(), nil)
	require.NoError(err)

	query, err := client.CreateQuery(context.Background(), &redash.CreateQueryInput{
		DataSourceID: ds.ID,
		Name:         "test-query-1",
		Query:        "select 1",
		Tags:         []string{"my-tag-1"},
	})
	require.NoError(err)
	assert.Equal("test-query-1", query.Name)
	assert.Equal([]string{"my-tag-1"}, query.Tags)
	assert.True(query.IsDraft)

	isDraft := false
	query, err = client.UpdateQuery(context.Background(), query.ID, &redash.UpdateQueryInput{
		Schedule: &redash.UpdateQueryInputSchedule{
			Interval: 600,
		},
		Tags:    &[]string{"my-tag-2"},
		IsDraft: &isDraft,
	})
	require.NoError(err)
	assert.Equal(&redash.QueueSchedule{Interval: 600}, query.Schedule)
	assert.Equal([]string{"my-tag-2"}, query.Tags)
	assert.False(query.IsDraft)

	query, err = client.UpdateQuery(context.Background(), query.ID, &redash.UpdateQueryInput{
		Schedule: &redash.UpdateQueryInputSchedule{
			Interval: 600,
		},
	})
	require.NoError(err)
	assert.Equal(&redash.QueueSchedule{Interval: 600}, query.Schedule)
	assert.Equal([]string{"my-tag-2"}, query.Tags)

	tags, err := client.GetQueryTags(context.Background())
	require.NoError(err)
	assert.GreaterOrEqual(len(tags.Tags), 1)

	query, err = client.UpdateQuery(context.Background(), query.ID, &redash.UpdateQueryInput{
		Tags: &[]string{},
	})
	require.NoError(err)
	assert.Equal("test-query-1", query.Name)
	assert.Equal(&redash.QueueSchedule{Interval: 600}, query.Schedule)
	assert.Equal([]string{}, query.Tags)

	tags, err = client.GetQueryTags(context.Background())
	require.NoError(err)
	assert.GreaterOrEqual(len(tags.Tags), 0)

	query, err = client.GetQuery(context.Background(), query.ID)
	require.NoError(err)
	assert.Equal("test-query-1", query.Name)

	page, err := client.SearchQueries(context.Background(), &redash.SearchQueriesInput{
		Q: "test-query-1",
	})
	require.NoError(err)
	assert.GreaterOrEqual(len(page.Results), 1)

	page, err = client.ListQueries(context.Background(), &redash.ListQueriesInput{
		Q: "test-query-1",
	})
	require.NoError(err)
	assert.GreaterOrEqual(len(page.Results), 1)

	page, err = client.ListMyQueries(context.Background(), &redash.ListMyQueriesInput{
		Q: "test-query-1",
	})
	require.NoError(err)
	assert.GreaterOrEqual(len(page.Results), 1)

	page, err = client.ListFavoriteQueries(context.Background(), &redash.ListFavoriteQueriesInput{
		Q: "test-query-1",
	})
	require.NoError(err)
	assert.Zero(len(page.Results))

	err = client.CreateFavoriteQuery(context.Background(), query.ID)
	require.NoError(err)

	page, err = client.ListFavoriteQueries(context.Background(), &redash.ListFavoriteQueriesInput{
		Q: "test-query-1",
	})
	require.NoError(err)
	assert.GreaterOrEqual(len(page.Results), 1)

	var buf bytes.Buffer
	job, err := client.ExecQueryJSON(context.Background(), query.ID, &redash.ExecQueryJSONInput{}, &buf)
	require.NoError(err)

	if job != nil && job.Job.ID != "" {
		for {
			job, err := client.GetJob(context.Background(), job.Job.ID)
			require.NoError(err)

			if job.Job.Status != redash.JobStatusPending && job.Job.Status != redash.JobStatusStarted {
				assert.Equal(redash.JobStatusSuccess, job.Job.Status)
				buf = bytes.Buffer{}
				err = client.GetQueryResultsJSON(context.Background(), query.ID, &buf)
				require.NoError(err)
				break
			}

			time.Sleep(1 * time.Second)
		}
	}

	assert.True(strings.HasPrefix(buf.String(), `{"query_result"`))

	{
		var rs map[string]map[string]any
		err := json.Unmarshal(buf.Bytes(), &rs)
		require.NoError(err)
		qeuryResultID := rs["query_result"]["id"].(float64)
		var buf bytes.Buffer
		err = client.GetQueryResultByID(context.Background(), int(qeuryResultID), "csv", &buf)
		require.NoError(err)
		assert.Equal("?column?\r\n1\r\n", buf.String())
	}

	_, err = client.ExecQueryJSON(context.Background(), query.ID, nil, nil)
	require.NoError(err)

	buf = bytes.Buffer{}
	err = client.GetQueryResultsCSV(context.Background(), query.ID, &buf)
	require.NoError(err)
	assert.Equal("?column?\r\n1\r\n", buf.String())

	job, err = client.RefreshQuery(context.Background(), query.ID, nil)
	require.NoError(err)

	if job != nil && job.Job.ID != "" {
		for {
			job, err := client.GetJob(context.Background(), job.Job.ID)
			require.NoError(err)

			if job.Job.Status != redash.JobStatusPending && job.Job.Status != redash.JobStatusStarted {
				assert.Equal(redash.JobStatusSuccess, job.Job.Status)
				buf = bytes.Buffer{}
				_, err := client.ExecQueryJSON(context.Background(), query.ID, nil, &buf)
				require.NoError(err)
				break
			}

			time.Sleep(1 * time.Second)
		}
	}

	assert.True(strings.HasPrefix(buf.String(), `{"query_result"`))

	job, err = client.RefreshQuery(context.Background(), query.ID, &redash.RefreshQueryInput{
		ApplyAutoLimit: true,
	})
	require.NoError(err)

	if job != nil && job.Job.ID != "" {
		for {
			job, err := client.GetJob(context.Background(), job.Job.ID)
			require.NoError(err)

			if job.Job.Status != redash.JobStatusPending && job.Job.Status != redash.JobStatusStarted {
				assert.Equal(redash.JobStatusSuccess, job.Job.Status)
				buf = bytes.Buffer{}
				_, err := client.ExecQueryJSON(context.Background(), query.ID, nil, &buf)
				require.NoError(err)
				break
			}

			time.Sleep(1 * time.Second)
		}
	}

	assert.True(strings.HasPrefix(buf.String(), `{"query_result"`))

	queries, err := client.ListRecentQueries(context.Background())
	require.NoError(err)
	assert.GreaterOrEqual(len(queries), 1)

	err = client.UnpublishQuery(context.Background(), query.ID)
	require.NoError(err)

	query, err = client.GetQuery(context.Background(), query.ID)
	require.NoError(err)
	assert.Equal("test-query-1", query.Name)
	assert.True(query.IsDraft)

	err = client.PublishQuery(context.Background(), query.ID)
	require.NoError(err)

	query, err = client.GetQuery(context.Background(), query.ID)
	require.NoError(err)
	assert.Equal("test-query-1", query.Name)
	assert.False(query.IsDraft)

	err = client.ArchiveQuery(context.Background(), query.ID)
	require.NoError(err)

	query, err = client.GetQuery(context.Background(), query.ID)
	require.NoError(err)
	assert.Equal("test-query-1", query.Name)
	assert.True(query.IsArchived)
	assert.True(query.IsFavorite)

	formatted, err := client.FormatQuery(context.Background(), "select 1 from dual")
	require.NoError(err)
	assert.Equal("SELECT 1\nFROM dual", formatted.Query)
}

func Test_Query_IgnoreCache_Acc(t *testing.T) {
	if !testAcc {
		t.Skip()
	}

	assert := assert.New(t)
	require := require.New(t)
	client, _ := redash.NewClient(testRedashEndpoint, testRedashAPIKey)
	ds, err := client.CreateDataSource(context.Background(), &redash.CreateDataSourceInput{
		Name: "test-postgres-1",
		Type: "pg",
		Options: map[string]any{
			"dbname": "postgres",
			"host":   "postgres",
			"port":   5432,
			"user":   "postgres",
		},
	})
	require.NoError(err)

	defer func() {
		client.DeleteDataSource(context.Background(), ds.ID) //nolint:errcheck
	}()

	_, err = client.ListQueries(context.Background(), nil)
	require.NoError(err)

	query, err := client.CreateQuery(context.Background(), &redash.CreateQueryInput{
		DataSourceID: ds.ID,
		Name:         "test-query-1",
		Query:        "select now()",
	})
	require.NoError(err)
	assert.Equal("test-query-1", query.Name)

	query, err = client.GetQuery(context.Background(), query.ID)
	require.NoError(err)
	assert.Equal("test-query-1", query.Name)
	assert.Equal("select now()", query.Query)
	assert.Equal(redash.QueryOptions{}, query.Options)
	rNow := regexp.MustCompile(`"now": "([^"]+)"`)
	var cachedNow string

	// Cache 1
	{
		var buf bytes.Buffer
		input := &redash.ExecQueryJSONInput{
			MaxAge: 1800,
		}
		job, err := client.ExecQueryJSON(context.Background(), query.ID, input, &buf)
		require.NoError(err)

		if job != nil && job.Job.ID != "" {
			for {
				job, err := client.GetJob(context.Background(), job.Job.ID)
				require.NoError(err)

				if job.Job.Status != redash.JobStatusPending && job.Job.Status != redash.JobStatusStarted {
					assert.Equal(redash.JobStatusSuccess, job.Job.Status)
					_, err := client.ExecQueryJSON(context.Background(), query.ID, input, &buf)
					require.NoError(err)
					break
				}

				time.Sleep(1 * time.Second)
			}
		}

		require.Contains(buf.String(), `"rows": [{"now": "`)
		m := rNow.FindStringSubmatch(buf.String())
		cachedNow = m[1]
	}

	{
		out, err := client.GetQueryResultsStruct(context.Background(), query.ID)
		require.NoError(err)
		require.Len(out.QueryResult.Data.Rows, 1)
		now, ok := out.QueryResult.Data.Rows[0]["now"].(string)
		require.True(ok)
		assert.Equal(cachedNow, now)
	}

	{
		out, err := client.GetQueryResultsStruct(context.Background(), query.ID)
		require.NoError(err)
		require.Len(out.QueryResult.Data.Rows, 1)
		now, ok := out.QueryResult.Data.Rows[0]["now"].(string)
		require.True(ok)
		assert.Equal(cachedNow, now)
	}

	// Cache 2
	{
		var buf bytes.Buffer
		job, err := client.ExecQueryJSON(context.Background(), query.ID, nil, &buf)
		require.NoError(err)
		require.Nil(job) // Get results from cache
		require.Contains(buf.String(), `"rows": [{"now": "`)
		m := rNow.FindStringSubmatch(buf.String())
		assert.Equal(cachedNow, m[1])
	}

	{
		out, err := client.GetQueryResultsStruct(context.Background(), query.ID)
		require.NoError(err)
		require.Len(out.QueryResult.Data.Rows, 1)
		now, ok := out.QueryResult.Data.Rows[0]["now"].(string)
		require.True(ok)
		assert.Equal(cachedNow, now)
	}

	// Ignore cache
	{
		var buf bytes.Buffer
		input := &redash.ExecQueryJSONInput{
			WithoutOmittingMaxAge: true,
		}
		job, err := client.ExecQueryJSON(context.Background(), query.ID, input, &buf)
		require.NoError(err)

		if job != nil && job.Job.ID != "" {
			for {
				job, err := client.GetJob(context.Background(), job.Job.ID)
				require.NoError(err)

				if job.Job.Status != redash.JobStatusPending && job.Job.Status != redash.JobStatusStarted {
					assert.Equal(redash.JobStatusSuccess, job.Job.Status)
					_, err := client.ExecQueryJSON(context.Background(), query.ID, input, &buf)
					require.NoError(err)
					break
				}

				time.Sleep(1 * time.Second)
			}
		}

		// NOTE: No result is returned if `max_age=0`.
		//       I don't know if this is the spec.
		assert.Empty(buf.String())
	}

	{
		out, err := client.GetQueryResultsStruct(context.Background(), query.ID)
		require.NoError(err)
		require.Len(out.QueryResult.Data.Rows, 1)
		now, ok := out.QueryResult.Data.Rows[0]["now"].(string)
		require.True(ok)
		assert.NotEqual(cachedNow, now)
	}
}

func Test_WaitQueryJSON_Acc(t *testing.T) {
	if !testAcc {
		t.Skip()
	}

	assert := assert.New(t)
	require := require.New(t)
	client, _ := redash.NewClient(testRedashEndpoint, testRedashAPIKey)
	ds, err := client.CreateDataSource(context.Background(), &redash.CreateDataSourceInput{
		Name: "test-postgres-1",
		Type: "pg",
		Options: map[string]any{
			"dbname": "postgres",
			"host":   "postgres",
			"port":   5432,
			"user":   "postgres",
		},
	})
	require.NoError(err)

	defer func() {
		client.DeleteDataSource(context.Background(), ds.ID) //nolint:errcheck
	}()

	_, err = client.ListQueries(context.Background(), nil)
	require.NoError(err)

	query, err := client.CreateQuery(context.Background(), &redash.CreateQueryInput{
		DataSourceID: ds.ID,
		Name:         "test-query-1",
		Query:        "select 1",
	})
	require.NoError(err)
	assert.Equal("test-query-1", query.Name)

	var buf bytes.Buffer
	job, err := client.ExecQueryJSON(context.Background(), query.ID, nil, &buf)
	require.NoError(err)
	err = client.WaitQueryJSON(context.Background(), query.ID, job, nil, &buf)
	require.NoError(err)
	assert.True(strings.HasPrefix(buf.String(), `{"query_result"`))

	buf.Reset()
	job, err = client.ExecQueryJSON(context.Background(), query.ID, nil, &buf)
	require.NoError(err)
	err = client.WaitQueryJSON(context.Background(), query.ID, job, &redash.WaitQueryJSONOption{
		WaitStatuses: []int{redash.JobStatusPending, redash.JobStatusStarted},
		Interval:     500 * time.Microsecond,
	}, &buf)
	require.NoError(err)
	assert.True(strings.HasPrefix(buf.String(), `{"query_result"`))
}

func Test_WaitQueryStruct_Acc(t *testing.T) {
	if !testAcc {
		t.Skip()
	}

	assert := assert.New(t)
	require := require.New(t)
	client, _ := redash.NewClient(testRedashEndpoint, testRedashAPIKey)
	ds, err := client.CreateDataSource(context.Background(), &redash.CreateDataSourceInput{
		Name: "test-postgres-1",
		Type: "pg",
		Options: map[string]any{
			"dbname": "postgres",
			"host":   "postgres",
			"port":   5432,
			"user":   "postgres",
		},
	})
	require.NoError(err)

	defer func() {
		client.DeleteDataSource(context.Background(), ds.ID) //nolint:errcheck
	}()

	_, err = client.ListQueries(context.Background(), nil)
	require.NoError(err)

	query, err := client.CreateQuery(context.Background(), &redash.CreateQueryInput{
		DataSourceID: ds.ID,
		Name:         "test-query-1",
		Query:        "select 1",
	})
	require.NoError(err)
	assert.Equal("test-query-1", query.Name)

	var buf bytes.Buffer
	job, err := client.ExecQueryJSON(context.Background(), query.ID, nil, &buf)
	require.NoError(err)
	out, err := client.WaitQueryStruct(context.Background(), query.ID, job, &redash.WaitQueryJSONOption{
		WaitStatuses: []int{redash.JobStatusPending, redash.JobStatusStarted},
		Interval:     500 * time.Microsecond,
	}, &buf)
	require.NoError(err)
	assert.Equal("select 1", out.QueryResult.Query)
}