package oncall

import (
	"context"
	"io"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/go-logr/logr"
	"github.com/go-logr/zapr"
	"github.com/go-resty/resty/v2"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/aisupport/packages/sdk-go/infra/logger"
	"github.com/tidbcloud/aisupport/services/ticket-manager/config"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"go.uber.org/zap/zaptest/observer"
)

func TestNewClientAddsHTTPSchemeForServiceAddress(t *testing.T) {
	t.Parallel()

	client := NewClient(config.OncallConfig{Addr: "oncall-service:8080", APIKey: "test-key", Timeout: time.Second}).(*httpClient)
	require.Equal(t, "http://oncall-service:8080", client.resty.BaseURL)
}

func TestOncallClientDebugLogsRequestAndResponseWithoutHeaders(t *testing.T) {
	t.Parallel()

	client := NewClient(config.OncallConfig{
		Addr:    "oncall-service:8080",
		APIKey:  "test-key",
		Timeout: time.Second,
		Debug:   true,
	}).(*httpClient)
	client.resty.SetTransport(roundTripFunc(func(request *http.Request) *http.Response {
		require.Equal(t, "Bearer test-key", request.Header.Get("Authorization"))
		return jsonResponse(http.StatusCreated, `{"ok":true}`)
	}))

	observedLogger, logs := newObservedLogger()
	ctx := logger.NewContext(context.Background(), observedLogger)
	response, err := client.resty.R().
		SetContext(ctx).
		SetQueryParam("team_id", "1").
		SetBody(`{"operation":"sync"}`).
		Post("/api/v1/debug?source=test")
	require.NoError(t, err)
	require.Equal(t, http.StatusCreated, response.StatusCode())

	entries := logs.AllUntimed()
	require.Len(t, entries, 2)
	require.Equal(t, "oncall request", entries[0].Message)
	require.Equal(t, map[string]any{
		"trace_id": "none",
		"method":   http.MethodPost,
		"url":      "http://oncall-service:8080/api/v1/debug?source=test&team_id=1",
		"body":     `{"operation":"sync"}`,
	}, entries[0].ContextMap())
	require.Equal(t, "oncall response", entries[1].Message)
	require.Equal(t, map[string]any{
		"trace_id":    "none",
		"status_code": int64(http.StatusCreated),
		"body":        `{"ok":true}`,
	}, entries[1].ContextMap())
	require.NotContains(t, entries[0].ContextMap(), "Authorization")
	require.NotContains(t, entries[0].ContextMap(), "test-key")
}

func TestOncallClientDoesNotLogWhenDebugDisabled(t *testing.T) {
	t.Parallel()

	client := NewClient(config.OncallConfig{
		Addr:    "oncall-service:8080",
		APIKey:  "test-key",
		Timeout: time.Second,
	}).(*httpClient)
	client.resty.SetTransport(roundTripFunc(func(_ *http.Request) *http.Response {
		return jsonResponse(http.StatusOK, `{"teams":[]}`)
	}))

	observedLogger, logs := newObservedLogger()
	ctx := logger.NewContext(context.Background(), observedLogger)
	_, err := client.ListTeams(ctx)
	require.NoError(t, err)
	require.Empty(t, logs.AllUntimed())
}

func TestListTeams(t *testing.T) {
	t.Parallel()

	client := newTestClient(t, func(request *http.Request) *http.Response {
		require.Equal(t, "Bearer test-key", request.Header.Get("Authorization"))
		require.Equal(t, "/api/v1/teams", request.URL.Path)
		return jsonResponse(http.StatusOK, `{"teams":[{"team_id":1,"team_key":"support-l1","team_name":"Support L1"},{"team_id":2,"team_key":"support-l2","team_name":"Support L2"}]}`)
	})

	teams, err := client.ListTeams(context.Background())
	require.NoError(t, err)
	require.Equal(t, []Team{
		{TeamID: 1, TeamKey: "support-l1", TeamName: "Support L1"},
		{TeamID: 2, TeamKey: "support-l2", TeamName: "Support L2"},
	}, teams)
}

func TestListTeamsReturnsHTTPError(t *testing.T) {
	t.Parallel()

	client := newTestClient(t, func(_ *http.Request) *http.Response {
		return jsonResponse(http.StatusServiceUnavailable, `{"error":"sensitive detail"}`)
	})

	_, err := client.ListTeams(context.Background())
	require.EqualError(t, err, "list oncall teams: oncall returned HTTP 503")
}

func TestListTeamMembers(t *testing.T) {
	t.Parallel()

	client := newTestClient(t, func(request *http.Request) *http.Response {
		require.Equal(t, "Bearer test-key", request.Header.Get("Authorization"))
		require.Equal(t, "/api/v1/teams/GS%20L1%2FTeam/available-engineers", request.URL.EscapedPath())
		return jsonResponse(http.StatusOK, `{
			"team_id":1,
			"engineers":[
				{"engineer_id":1,"engineer_email":"ALICE@example.com","engineer_name":"Alice","is_manager":true},
				{"engineer_id":2,"engineer_email":"Bob@example.com","engineer_name":"Bob","is_manager":false}
			]
		}`)
	})

	members, err := client.ListTeamMembers(context.Background(), "GS L1/Team")
	require.NoError(t, err)
	require.Equal(t, []string{"alice@example.com", "bob@example.com"}, members)
}

func TestListTeamMembersReturnsHTTPError(t *testing.T) {
	t.Parallel()

	client := newTestClient(t, func(_ *http.Request) *http.Response {
		return jsonResponse(http.StatusServiceUnavailable, `{"error":"sensitive detail"}`)
	})

	_, err := client.ListTeamMembers(context.Background(), "support")
	require.EqualError(t, err, "list oncall team members: oncall returned HTTP 503")
}

func TestListSchedule(t *testing.T) {
	t.Parallel()

	startTime := time.Date(2026, 7, 10, 2, 0, 0, 0, time.UTC)
	endTime := startTime.Add(time.Second)
	client := newTestClient(t, func(request *http.Request) *http.Response {
		require.Equal(t, "Bearer test-key", request.Header.Get("Authorization"))
		require.Equal(t, "/api/v1/schedule", request.URL.Path)
		require.Equal(t, "1,2", request.URL.Query().Get("team_ids"))
		require.Equal(t, "1783648800", request.URL.Query().Get("start_time"))
		require.Equal(t, "1783648801", request.URL.Query().Get("end_time"))
		require.Empty(t, request.URL.Query().Get("is_backup"))
		return jsonResponse(http.StatusOK, `{"rows":[{"engineer_email":"ALICE@example.com","start_time":1783648800,"end_time":1783648801}]}`)
	})

	schedule, err := client.ListSchedule(context.Background(), ListScheduleParam{
		TeamIDs:   []uint64{1, 2},
		StartTime: startTime,
		EndTime:   endTime,
	})
	require.NoError(t, err)
	require.Equal(t, []Schedule{{
		EngineerEmail: "ALICE@example.com",
		StartTime:     startTime.Unix(),
		EndTime:       endTime.Unix(),
	}}, schedule)
}

func TestGetScheduleRespondersUsesPrimaryOnly(t *testing.T) {
	t.Parallel()

	at := time.Date(2026, 7, 10, 3, 0, 0, 0, time.UTC)
	client := newTestClient(t, func(request *http.Request) *http.Response {
		require.Equal(t, "/api/v1/teams/GS L1 Team_schedule/current-oncall", request.URL.Path)
		require.Equal(t, "1783652400", request.URL.Query().Get("at_time"))
		return jsonResponse(http.StatusOK, `{
			"current_engineer_emails":["ALICE@example.com"],
			"backup_engineer_emails":["backup@example.com"]
		}`)
	})

	responders, err := client.GetScheduleResponders(context.Background(), "GS L1 Team_schedule", at)
	require.NoError(t, err)
	require.Equal(t, []string{"alice@example.com"}, responders)
}

func TestPage(t *testing.T) {
	t.Parallel()

	client := newTestClient(t, func(request *http.Request) *http.Response {
		require.Equal(t, http.MethodPost, request.Method)
		require.Equal(t, "/api/v1/page", request.URL.Path)
		body, err := io.ReadAll(request.Body)
		require.NoError(t, err)
		require.JSONEq(t, `{"user_email":"alice@example.com","reason":"urgent ticket"}`, string(body))
		return jsonResponse(http.StatusOK, `{"message_id":"om_page","phone_number":"+15551234567","call_method":"twilio"}`)
	})

	result, err := client.Page(context.Background(), PageRequest{UserEmail: "alice@example.com", Reason: "urgent ticket"})
	require.NoError(t, err)
	require.Equal(t, "om_page", result.MessageID)
	require.Equal(t, "+15551234567", result.PhoneNumber)
	require.Equal(t, "twilio", result.CallMethod)
}

func TestPageDebugLogRedactsPhoneNumber(t *testing.T) {
	t.Parallel()

	client := NewClient(config.OncallConfig{Addr: "oncall-service:8080", APIKey: "test-key", Timeout: time.Second, Debug: true}).(*httpClient)
	client.resty.SetTransport(roundTripFunc(func(_ *http.Request) *http.Response {
		return jsonResponse(http.StatusOK, `{"message_id":"om_page","phone_number":"+15551234567","call_method":"twilio"}`)
	}))
	observedLogger, logs := newObservedLogger()
	ctx := logger.NewContext(context.Background(), observedLogger)
	_, err := client.Page(ctx, PageRequest{UserEmail: "alice@example.com", Reason: "urgent ticket"})
	require.NoError(t, err)
	entries := logs.AllUntimed()
	require.Len(t, entries, 2)
	require.Equal(t, "oncall page response", entries[1].Message)
	require.NotContains(t, entries[1].ContextMap(), "body")
	require.NotContains(t, entries[1].ContextMap(), "+15551234567")
}

func TestOncallClientReturnsHTTPErrorWithoutResponseBody(t *testing.T) {
	t.Parallel()

	client := newTestClient(t, func(_ *http.Request) *http.Response {
		return jsonResponse(http.StatusServiceUnavailable, `{"error":"sensitive detail"}`)
	})
	_, err := client.GetScheduleResponders(context.Background(), "support", time.Now())
	require.EqualError(t, err, "get schedule responders: oncall returned HTTP 503")
}

type roundTripFunc func(*http.Request) *http.Response

func (f roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
	return f(request), nil
}

func newTestClient(t *testing.T, responder roundTripFunc) Client {
	t.Helper()
	client := resty.New().
		SetBaseURL("http://oncall.test").
		SetAuthToken("test-key").
		SetTransport(responder)
	return &httpClient{resty: client}
}

func jsonResponse(status int, body string) *http.Response {
	return &http.Response{
		StatusCode: status,
		Header:     http.Header{"Content-Type": []string{"application/json"}},
		Body:       io.NopCloser(strings.NewReader(body)),
	}
}

func newObservedLogger() (logr.Logger, *observer.ObservedLogs) {
	core, logs := observer.New(zapcore.DebugLevel)
	return zapr.NewLogger(zap.New(core)), logs
}
