// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tools

import (
	"strings"
	"testing"
	"time"

	api "skywalking.apache.org/repo/goapi/query"
)

const (
	// testTimeStart / testTimeEnd are the formatted minute-step values for
	// now=2026-03-31T12:00 UTC minus/plus 2 h, used across multiple tests here.
	testTimeStart = "2026-03-31 1000"
	testTimeEnd   = "2026-03-31 1200"
)

func TestFinalizeURL(t *testing.T) {
	tests := []struct {
		name string
		in   string
		want string
	}{
		{name: "adds graphql suffix", in: "http://localhost:12800", want: "http://localhost:12800/graphql"},
		{name: "trims trailing slash", in: "http://localhost:12800/", want: "http://localhost:12800/graphql"},
		{name: "keeps existing graphql", in: "http://localhost:12800/graphql", want: "http://localhost:12800/graphql"},
		{name: "preserves query string", in: "http://localhost:12800?x=1", want: "http://localhost:12800/graphql?x=1"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got := FinalizeURL(tc.in); got != tc.want {
				t.Fatalf("FinalizeURL(%q) = %q, want %q", tc.in, got, tc.want)
			}
		})
	}
}

func TestNormalizeOAPURL(t *testing.T) {
	tests := []struct {
		name    string
		in      string
		want    string
		wantErr string
	}{
		{name: "http", in: "http://localhost:12800", want: "http://localhost:12800/graphql"},
		{name: "https", in: "https://localhost:12800/graphql", want: "https://localhost:12800/graphql"},
		{name: "preserves query and fragment", in: "https://localhost:12800/oap?debug=1#frag", want: "https://localhost:12800/oap/graphql?debug=1#frag"},
		{name: "rejects unsupported scheme", in: "ftp://localhost:12800", wantErr: "unsupported OAP URL scheme \"ftp\""},
		{name: "rejects missing host", in: "http://", wantErr: "host is required"},
		{name: "rejects malformed hostless path", in: "http:/foo", wantErr: "host is required"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got, err := NormalizeOAPURL(tc.in)
			if tc.wantErr != "" {
				if err == nil {
					t.Fatalf("NormalizeOAPURL(%q) error = nil, want %q", tc.in, tc.wantErr)
				}
				if !strings.Contains(err.Error(), tc.wantErr) {
					t.Fatalf("NormalizeOAPURL(%q) error = %q, want substring %q", tc.in, err.Error(), tc.wantErr)
				}
				return
			}

			if err != nil {
				t.Fatalf("NormalizeOAPURL(%q) unexpected error: %v", tc.in, err)
			}
			if got != tc.want {
				t.Fatalf("NormalizeOAPURL(%q) = %q, want %q", tc.in, got, tc.want)
			}
		})
	}
}

func TestParseTimezoneOffset(t *testing.T) {
	loc, ok := parseTimezoneOffset("+0830")
	if !ok {
		t.Fatal("expected timezone to parse")
	}
	if got := loc.String(); got != "+0830" {
		t.Fatalf("location name = %q", got)
	}

	_, ok = parseTimezoneOffset("UTC")
	if ok {
		t.Fatal("expected invalid timezone offset to fail")
	}
}

func TestParseDurationWithContextRelativeDuration(t *testing.T) {
	now := time.Date(2026, 3, 31, 12, 0, 0, 0, time.UTC)
	timeCtx := TimeContext{NowUTC: now, Location: time.UTC}

	got := ParseDurationWithContext("-2h", false, timeCtx)

	if got.Start != testTimeStart {
		t.Fatalf("start = %q", got.Start)
	}
	if got.End != testTimeEnd {
		t.Fatalf("end = %q", got.End)
	}
	if got.Step != api.StepMinute {
		t.Fatalf("step = %q", got.Step)
	}
}

func TestParseDurationWithContextLegacyDays(t *testing.T) {
	now := time.Date(2026, 3, 31, 12, 0, 0, 0, time.UTC)
	timeCtx := TimeContext{NowUTC: now, Location: time.UTC}

	got := ParseDurationWithContext("7d", true, timeCtx)

	if got.Start != "2026-03-24" {
		t.Fatalf("start = %q", got.Start)
	}
	if got.End != "2026-03-31" {
		t.Fatalf("end = %q", got.End)
	}
	if got.Step != api.StepDay {
		t.Fatalf("step = %q", got.Step)
	}
	if got.ColdStage == nil || !*got.ColdStage {
		t.Fatal("expected cold stage to be true")
	}
}

func TestBuildPaginationDefaultsAndCustomValues(t *testing.T) {
	gotDefault := BuildPagination(0, 0)
	if gotDefault.PageNum == nil || *gotDefault.PageNum != DefaultPageNum {
		t.Fatalf("default page num = %v", gotDefault.PageNum)
	}
	if gotDefault.PageSize != DefaultPageSize {
		t.Fatalf("default page size = %d", gotDefault.PageSize)
	}

	gotCustom := BuildPagination(3, 50)
	if gotCustom.PageNum == nil || *gotCustom.PageNum != 3 {
		t.Fatalf("custom page num = %v", gotCustom.PageNum)
	}
	if gotCustom.PageSize != 50 {
		t.Fatalf("custom page size = %d", gotCustom.PageSize)
	}
}

func TestBuildDurationWithContextParsesAbsoluteTimes(t *testing.T) {
	timeCtx := TimeContext{
		NowUTC:   time.Date(2026, 3, 31, 12, 0, 0, 0, time.UTC),
		Location: time.FixedZone("+0800", 8*3600),
	}

	got := BuildDurationWithContext("2026-03-31 18:00:00", "2026-03-31 20:00:00", "", false, 30, timeCtx)

	if got.Start != testTimeStart {
		t.Fatalf("start = %q", got.Start)
	}
	if got.End != testTimeEnd {
		t.Fatalf("end = %q", got.End)
	}
	if got.Step != api.StepMinute {
		t.Fatalf("step = %q", got.Step)
	}
}
