import Testing
import Foundation
@testable import TaskStore

@Suite("VTODOParser")
struct VTODOParserTests {

	// MARK: - Parsing

	@Test("parses all standard VTODO properties")
	func parseAllProperties() {
		let ics = """
		BEGIN:VCALENDAR\r
		VERSION:2.0\r
		BEGIN:VTODO\r
		UID:task-001\r
		SUMMARY:Buy groceries\r
		DESCRIPTION:Milk\\, eggs\\, bread\r
		STATUS:NEEDS-ACTION\r
		PRIORITY:5\r
		DUE;VALUE=DATE:20260315\r
		DTSTART;VALUE=DATE:20260310\r
		CATEGORIES:errands,shopping\r
		ATTACH:mid:abc123@example.com\r
		X-MAGNUM-SOMEDAY:TRUE\r
		CREATED:20260301T090000Z\r
		LAST-MODIFIED:20260305T120000Z\r
		END:VTODO\r
		END:VCALENDAR\r
		"""

		let props = VTODOParser.parse(ics)
		#expect(props != nil)
		#expect(props?["UID"] == "task-001")
		#expect(props?["SUMMARY"] == "Buy groceries")
		#expect(props?["DESCRIPTION"] == "Milk, eggs, bread")
		#expect(props?["STATUS"] == "NEEDS-ACTION")
		#expect(props?["PRIORITY"] == "5")
		#expect(props?["DUE"] == "20260315")
		#expect(props?["DTSTART"] == "20260310")
		#expect(props?["CATEGORIES"] == "errands,shopping")
		#expect(props?["ATTACH"] == "mid:abc123@example.com")
		#expect(props?["X-MAGNUM-SOMEDAY"] == "TRUE")
		#expect(props?["CREATED"] == "20260301T090000Z")
		#expect(props?["LAST-MODIFIED"] == "20260305T120000Z")
	}

	@Test("returns nil for invalid content without VTODO")
	func invalidContentReturnsNil() {
		let ics = "BEGIN:VCALENDAR\nVERSION:2.0\nEND:VCALENDAR"
		#expect(VTODOParser.parse(ics) == nil)
	}

	@Test("returns nil for empty string")
	func emptyStringReturnsNil() {
		#expect(VTODOParser.parse("") == nil)
	}

	@Test("handles line unfolding")
	func lineUnfolding() {
		let ics = "BEGIN:VTODO\r\nSUMMARY:This is a very lo\r\n ng summary that was folded\r\nEND:VTODO"
		let props = VTODOParser.parse(ics)
		#expect(props?["SUMMARY"] == "This is a very long summary that was folded")
	}

	@Test("strips parameters from property names")
	func stripsParameters() {
		let ics = "BEGIN:VTODO\r\nDTSTART;VALUE=DATE:20260315\r\nDUE;VALUE=DATE:20260320\r\nEND:VTODO"
		let props = VTODOParser.parse(ics)
		#expect(props?["DTSTART"] == "20260315")
		#expect(props?["DUE"] == "20260320")
	}

	// MARK: - Date parsing

	@Test("parses DATE-TIME with Z")
	func parseDateTimeUTC() {
		let date = VTODOParser.parseDate("20260315T090000Z")
		#expect(date != nil)

		let calendar = Calendar(identifier: .gregorian)
		let components = calendar.dateComponents(in: TimeZone(identifier: "UTC")!, from: date!)
		#expect(components.year == 2026)
		#expect(components.month == 3)
		#expect(components.day == 15)
		#expect(components.hour == 9)
		#expect(components.minute == 0)
	}

	@Test("parses DATE-TIME without Z")
	func parseDateTimeLocal() {
		let date = VTODOParser.parseDate("20260315T090000")
		#expect(date != nil)
	}

	@Test("parses DATE only")
	func parseDateOnly() {
		let date = VTODOParser.parseDate("20260315")
		#expect(date != nil)

		let calendar = Calendar(identifier: .gregorian)
		let components = calendar.dateComponents(in: TimeZone(identifier: "UTC")!, from: date!)
		#expect(components.year == 2026)
		#expect(components.month == 3)
		#expect(components.day == 15)
	}

	@Test("returns nil for invalid date")
	func invalidDate() {
		#expect(VTODOParser.parseDate("not-a-date") == nil)
		#expect(VTODOParser.parseDate("") == nil)
	}

	// MARK: - Date round-trip

	@Test("date round-trip preserves value")
	func dateRoundTrip() {
		let original = Date(timeIntervalSince1970: 1_773_763_200) // 2026-03-15T12:00:00Z
		let formatted = VTODOParser.formatDate(original)
		let parsed = VTODOParser.parseDate(formatted)
		#expect(parsed != nil)
		#expect(abs(parsed!.timeIntervalSince(original)) < 1)
	}

	@Test("date-only round-trip preserves value")
	func dateOnlyRoundTrip() {
		let original = VTODOParser.parseDate("20260315")!
		let formatted = VTODOParser.formatDateOnly(original)
		#expect(formatted == "20260315")
	}

	// MARK: - Unescape round-trip

	@Test("unescape handles all special characters")
	func unescapeSpecialChars() {
		#expect(VTODOParser.unescapeText("hello\\nworld") == "hello\nworld")
		#expect(VTODOParser.unescapeText("a\\,b") == "a,b")
		#expect(VTODOParser.unescapeText("a\\;b") == "a;b")
		#expect(VTODOParser.unescapeText("a\\\\b") == "a\\b")
		#expect(VTODOParser.unescapeText("plain text") == "plain text")
	}

	@Test("unescape handles uppercase N for newline")
	func unescapeUppercaseN() {
		#expect(VTODOParser.unescapeText("line1\\Nline2") == "line1\nline2")
	}
}
