// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package caldavtests

import (
	"strings"
	"testing"

	ics "github.com/arran4/golang-ical"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestReportCalendarQuery(t *testing.T) {
	// RFC 4791 §7.8 (rfc4791.txt line 1967):
	// "The CALDAV:calendar-query REPORT performs a search for all calendar
	//  object resources that match a specified filter."

	t.Run("calendar-query returns 207 Multi-Status", func(t *testing.T) {
		e := setupTestEnv(t)

		rec := caldavREPORT(t, e, "/dav/projects/36", ReportCalendarQuery)

		assertResponseStatus(t, rec, 207)
	})

	t.Run("calendar-query returns all tasks in project", func(t *testing.T) {
		e := setupTestEnv(t)

		rec := caldavREPORT(t, e, "/dav/projects/36", ReportCalendarQuery)

		assertResponseStatus(t, rec, 207)
		ms := parseMultistatus(t, rec)

		// Project 36 has 5 tasks in fixtures (40, 41, 42, 43, 45)
		assert.Len(t, ms.Responses, 5,
			"Should return all 5 tasks from project 36")
	})

	t.Run("calendar-query responses include ETag", func(t *testing.T) {
		e := setupTestEnv(t)

		rec := caldavREPORT(t, e, "/dav/projects/36", ReportCalendarQuery)

		ms := parseMultistatus(t, rec)
		for _, r := range ms.Responses {
			prop := getSuccessfulProp(t, r)
			assert.NotEmpty(t, prop.GetETag,
				"Each response should include an ETag. Href: %s", r.Href)
		}
	})

	t.Run("calendar-query responses include valid calendar-data", func(t *testing.T) {
		e := setupTestEnv(t)

		rec := caldavREPORT(t, e, "/dav/projects/36", ReportCalendarQuery)

		ms := parseMultistatus(t, rec)
		for i, r := range ms.Responses {
			prop := getSuccessfulProp(t, r)
			assert.NotEmpty(t, prop.CalendarData,
				"Response %d should include calendar-data. Href: %s", i, r.Href)

			// Each calendar-data should be parseable iCalendar
			cal := parseICalFromString(t, prop.CalendarData)
			vtodo := getVTodo(t, cal)

			uid := getVTodoProperty(vtodo, ics.ComponentPropertyUniqueId)
			assert.NotEmpty(t, uid, "VTODO %d should have a UID", i)

			summary := getVTodoProperty(vtodo, ics.ComponentPropertySummary)
			assert.NotEmpty(t, summary, "VTODO %d should have a SUMMARY", i)
		}
	})

	t.Run("calendar-query response hrefs point to correct resources", func(t *testing.T) {
		e := setupTestEnv(t)

		rec := caldavREPORT(t, e, "/dav/projects/36", ReportCalendarQuery)

		ms := parseMultistatus(t, rec)
		for _, r := range ms.Responses {
			// Each href should be a valid task URL containing the project ID and .ics
			assert.Contains(t, r.Href, "/dav/projects/",
				"Href should contain /dav/projects/")
			assert.True(t, strings.HasSuffix(r.Href, ".ics"),
				"Href should end with .ics. Got: %s", r.Href)
		}
	})

	t.Run("calendar-query on nonexistent project returns error", func(t *testing.T) {
		e := setupTestEnv(t)

		rec := caldavREPORT(t, e, "/dav/projects/99999", ReportCalendarQuery)

		// Should be 404 or similar error, not 200/207
		assert.NotEqual(t, 207, rec.Code,
			"REPORT on nonexistent project should not return 207")
	})

	t.Run("calendar-query on project 38 returns correct task count", func(t *testing.T) {
		e := setupTestEnv(t)

		rec := caldavREPORT(t, e, "/dav/projects/38", ReportCalendarQuery)

		assertResponseStatus(t, rec, 207)
		ms := parseMultistatus(t, rec)
		// Project 38 has 2 tasks (44, 46)
		assert.Len(t, ms.Responses, 2,
			"Project 38 should have 2 tasks")
	})
}

func TestReportCalendarMultiget(t *testing.T) {
	// RFC 4791 §7.9 (rfc4791.txt line 3479):
	// "The CALDAV:calendar-multiget REPORT is used to retrieve specific
	//  calendar object resources from within a collection."

	t.Run("calendar-multiget returns requested tasks", func(t *testing.T) {
		e := setupTestEnv(t)

		// Request two specific tasks from project 36
		body := ReportCalendarMultiget(
			"/dav/projects/36/uid-caldav-test.ics",
			"/dav/projects/36/uid-caldav-test-parent-task.ics",
		)

		rec := caldavREPORT(t, e, "/dav/projects/36", body)

		assertResponseStatus(t, rec, 207)
		ms := parseMultistatus(t, rec)

		assert.Len(t, ms.Responses, 2,
			"Should return exactly the 2 requested tasks")
	})

	t.Run("calendar-multiget returns calendar-data for each task", func(t *testing.T) {
		e := setupTestEnv(t)

		body := ReportCalendarMultiget(
			"/dav/projects/36/uid-caldav-test.ics",
		)

		rec := caldavREPORT(t, e, "/dav/projects/36", body)

		assertResponseStatus(t, rec, 207)
		ms := parseMultistatus(t, rec)
		require.Len(t, ms.Responses, 1)

		prop := getSuccessfulProp(t, ms.Responses[0])
		assert.NotEmpty(t, prop.CalendarData, "Should include calendar-data")
		assert.NotEmpty(t, prop.GetETag, "Should include ETag")

		// Verify the returned data matches the requested task
		cal := parseICalFromString(t, prop.CalendarData)
		vtodo := getVTodo(t, cal)
		assert.Equal(t, "uid-caldav-test", getVTodoProperty(vtodo, ics.ComponentPropertyUniqueId))
	})

	t.Run("calendar-multiget with nonexistent href returns 404 for that href", func(t *testing.T) {
		e := setupTestEnv(t)

		body := ReportCalendarMultiget(
			"/dav/projects/36/uid-caldav-test.ics",
			"/dav/projects/36/nonexistent-uid.ics",
		)

		rec := caldavREPORT(t, e, "/dav/projects/36", body)

		assertResponseStatus(t, rec, 207)
		ms := parseMultistatus(t, rec)

		// Should still return results — the existing task should be there
		// The nonexistent one might be absent or have a 404 propstat
		foundExisting := false
		for _, r := range ms.Responses {
			if strings.Contains(r.Href, "uid-caldav-test") {
				foundExisting = true
				prop := getSuccessfulProp(t, r)
				assert.NotEmpty(t, prop.CalendarData)
			}
		}
		assert.True(t, foundExisting,
			"Should still return the existing task even when one href is invalid")
	})

	t.Run("calendar-multiget with empty href list returns empty", func(t *testing.T) {
		e := setupTestEnv(t)

		body := ReportCalendarMultiget() // No hrefs

		rec := caldavREPORT(t, e, "/dav/projects/36", body)

		// Should return 207 with no responses, or possibly an error
		assert.True(t, rec.Code == 207 || rec.Code >= 400,
			"Empty multiget should return 207 (empty) or an error, got %d", rec.Code)
	})

	t.Run("calendar-multiget ETags match PROPFIND ETags", func(t *testing.T) {
		t.Skip("Known bug: ETag format inconsistency between PROPFIND and REPORT responses in caldav-go")
		e := setupTestEnv(t)

		// Get ETag via PROPFIND
		propfindRec := caldavPROPFIND(t, e, "/dav/projects/36/uid-caldav-test.ics", "0", PropfindResourceProperties)
		assertResponseStatus(t, propfindRec, 207)
		propfindMs := parseMultistatus(t, propfindRec)
		propfindEtag := getSuccessfulProp(t, propfindMs.Responses[0]).GetETag

		// Get ETag via multiget REPORT
		body := ReportCalendarMultiget("/dav/projects/36/uid-caldav-test.ics")
		reportRec := caldavREPORT(t, e, "/dav/projects/36", body)
		assertResponseStatus(t, reportRec, 207)
		reportMs := parseMultistatus(t, reportRec)
		reportEtag := getSuccessfulProp(t, reportMs.Responses[0]).GetETag

		// ETags should match between PROPFIND and REPORT
		assert.Equal(t, propfindEtag, reportEtag,
			"ETag from PROPFIND and calendar-multiget should match")
	})
}
