//
//  DataManagerExtendedTests.swift
//  honeyDueTests
//
//  Extended unit tests covering TASK-005, TASK-012, THEME-001, TCOMP-003, and QA-002.
//
//  IMPORTANT: DataManager-mutating suites are nested inside the DataLayerTests
//  serialized parent (defined in DataLayerTests.swift) via extension, so ALL
//  DataManager tests serialize together and avoid concurrent state interference.
//

import Testing
import Foundation
@testable import honeyDue
import ComposeApp

// MARK: - Extension of DataLayerTests (serialized parent in DataLayerTests.swift)

extension DataLayerTests {

    // MARK: - TASK-005: Template Search Tests

    @Suite struct TemplateSearchTests {

        // Helper to build a TaskTemplate with the fields that searchTaskTemplates inspects.
        // categoryId and frequencyId are nullable Int in Kotlin; pass nil directly.
        private func makeTemplate(
            id: Int32,
            title: String,
            description: String = "",
            tags: [String] = []
        ) -> TaskTemplate {
            TaskTemplate(
                id: id,
                title: title,
                description: description,
                categoryId: nil,
                category: nil,
                frequencyId: nil,
                frequency: nil,
                iconIos: "",
                iconAndroid: "",
                tags: tags,
                displayOrder: 0,
                isActive: true
            )
        }

        @Test func searchWithSingleCharReturnsEmpty() {
            DataManager.shared.clear()
            DataManager.shared.setTaskTemplates(templates: [
                makeTemplate(id: 1, title: "Appliance check")
            ])

            let results = DataManager.shared.searchTaskTemplates(query: "A")
            #expect(results.isEmpty)

            DataManager.shared.clear()
        }

        @Test func searchWithTwoCharsMatchesTitles() {
            DataManager.shared.clear()
            DataManager.shared.setTaskTemplates(templates: [
                makeTemplate(id: 1, title: "Plumbing repair"),
                makeTemplate(id: 2, title: "HVAC inspection"),
                makeTemplate(id: 3, title: "Lawn care")
            ])

            let results = DataManager.shared.searchTaskTemplates(query: "Pl")
            #expect(results.count == 1)
            #expect(results.first?.title == "Plumbing repair")

            DataManager.shared.clear()
        }

        @Test func searchIsCaseInsensitive() {
            DataManager.shared.clear()
            DataManager.shared.setTaskTemplates(templates: [
                makeTemplate(id: 1, title: "Plumbing repair"),
                makeTemplate(id: 2, title: "Electrical panel check")
            ])

            let results = DataManager.shared.searchTaskTemplates(query: "plumbing")
            #expect(results.count == 1)
            #expect(results.first?.title == "Plumbing repair")

            DataManager.shared.clear()
        }

        @Test func searchMatchesDescription() {
            DataManager.shared.clear()
            DataManager.shared.setTaskTemplates(templates: [
                makeTemplate(id: 1, title: "Pipe maintenance", description: "Fix water leaks and pipe corrosion"),
                makeTemplate(id: 2, title: "Roof inspection", description: "Check shingles and gutters")
            ])

            let results = DataManager.shared.searchTaskTemplates(query: "water")
            #expect(results.count == 1)
            #expect(results.first?.title == "Pipe maintenance")

            DataManager.shared.clear()
        }

        @Test func searchMatchesTags() {
            DataManager.shared.clear()
            DataManager.shared.setTaskTemplates(templates: [
                makeTemplate(id: 1, title: "Air filter replacement", tags: ["hvac", "filter", "air quality"]),
                makeTemplate(id: 2, title: "Lawn mowing", tags: ["landscaping", "outdoor"])
            ])

            let results = DataManager.shared.searchTaskTemplates(query: "hvac")
            #expect(results.count == 1)
            #expect(results.first?.title == "Air filter replacement")

            DataManager.shared.clear()
        }

        @Test func searchReturnsMaxTenResults() {
            DataManager.shared.clear()

            // Create 15 templates all matching "maintenance"
            var templates: [TaskTemplate] = []
            for i in 1...15 {
                templates.append(makeTemplate(id: Int32(i), title: "maintenance task \(i)"))
            }
            DataManager.shared.setTaskTemplates(templates: templates)

            let results = DataManager.shared.searchTaskTemplates(query: "maintenance")
            #expect(results.count == 10)

            DataManager.shared.clear()
        }

        @Test func searchNoMatchReturnsEmpty() {
            DataManager.shared.clear()
            DataManager.shared.setTaskTemplates(templates: [
                makeTemplate(id: 1, title: "Plumbing repair"),
                makeTemplate(id: 2, title: "Roof inspection")
            ])

            let results = DataManager.shared.searchTaskTemplates(query: "xyz")
            #expect(results.isEmpty)

            DataManager.shared.clear()
        }

        @Test func emptyQueryReturnsEmpty() {
            DataManager.shared.clear()
            DataManager.shared.setTaskTemplates(templates: [
                makeTemplate(id: 1, title: "Plumbing repair")
            ])

            let results = DataManager.shared.searchTaskTemplates(query: "")
            #expect(results.isEmpty)

            DataManager.shared.clear()
        }
    }

    // MARK: - TASK-012: Remove Task Cache Updates

    @Suite struct RemoveTaskTests {

        @Test func allTasksIsNilAfterClear() {
            DataManager.shared.clear()

            let value = DataManager.shared.allTasks.value
            #expect(value == nil)
        }

        @Test func removeTaskOnNilAllTasksIsNoOp() {
            // When allTasks is nil, removeTask should not crash and allTasks remains nil.
            DataManager.shared.clear()

            DataManager.shared.removeTask(taskId: 42)

            let value = DataManager.shared.allTasks.value
            #expect(value == nil)

            DataManager.shared.clear()
        }

        @Test func tasksByResidenceIsEmptyAfterClear() {
            // After clear, tasksByResidence is empty — removeTask has no residence caches to update.
            // Calling removeTask with no tasks cached should be a no-op (no crash, allTasks stays nil).
            DataManager.shared.clear()

            // removeTask with empty caches must not throw or crash
            DataManager.shared.removeTask(taskId: 999)

            // allTasks should remain nil after a no-op removeTask on empty state
            #expect(DataManager.shared.allTasks.value == nil)
        }

        // NOTE: Full integration coverage for removeTask (removing from kanban columns and
        // residence caches) is exercised in the UI test suite (TaskIntegrationTests) where
        // real TaskColumnsResponse objects are constructed through the API layer.
        // Constructing TaskColumnsResponse directly from Swift requires bridging complex
        // Kotlin generics (Map<String,String> for icons) that are impractical in unit tests.
    }

    // MARK: - THEME-001: Theme Persistence Tests

    @Suite struct ThemePersistenceTests {

        @Test func defaultThemeIdIsDefault() {
            // Explicitly set to "default" first since clear() does NOT reset themeId.
            DataManager.shared.setThemeId(id: "default")
            DataManager.shared.clear()

            let themeId = DataManager.shared.themeId.value as! String
            #expect(themeId == "default")
        }

        @Test func setThemeIdUpdatesValue() {
            DataManager.shared.clear()

            DataManager.shared.setThemeId(id: "ocean")

            let themeId = DataManager.shared.themeId.value as! String
            #expect(themeId == "ocean")

            // Restore to default so other tests are unaffected
            DataManager.shared.setThemeId(id: "default")
        }

        @Test func setThemeIdToMultipleThemes() {
            DataManager.shared.clear()

            DataManager.shared.setThemeId(id: "forest")
            let intermediate = DataManager.shared.themeId.value as! String
            #expect(intermediate == "forest")

            DataManager.shared.setThemeId(id: "midnight")
            let final_ = DataManager.shared.themeId.value as! String
            #expect(final_ == "midnight")

            // Restore
            DataManager.shared.setThemeId(id: "default")
        }

        @Test func clearDoesNotResetTheme() {
            // Per CLAUDE.md architecture, clear() does NOT reset themeId.
            // The Kotlin source confirms _themeId is absent from the clear() method.
            DataManager.shared.setThemeId(id: "ocean")

            DataManager.shared.clear()

            // Theme should be preserved after clear — verify it is still a non-empty string.
            let themeId = DataManager.shared.themeId.value as! String
            #expect(!themeId.isEmpty)

            // Restore
            DataManager.shared.setThemeId(id: "default")
        }

        @Test func themeIdIsPreservedAsOceanAfterClear() {
            // Stronger assertion: the exact value set before clear() survives clear().
            DataManager.shared.setThemeId(id: "ocean")

            DataManager.shared.clear()

            let themeId = DataManager.shared.themeId.value as! String
            #expect(themeId == "ocean")

            // Restore
            DataManager.shared.setThemeId(id: "default")
        }
    }
}

// MARK: - TCOMP-003: Task Completion Form Validation
// These tests use only ValidationHelpers (pure Swift, no DataManager mutation).

struct TaskCompletionValidationTests {

    @Test func completedByFieldRequired() {
        let result = ValidationHelpers.validateRequired("", fieldName: "Completed by")
        #expect(!result.isValid)
        #expect(result.errorMessage == "Completed by is required")
    }

    @Test func completedByFieldWithValuePasses() {
        let result = ValidationHelpers.validateRequired("Trey Tartt", fieldName: "Completed by")
        #expect(result.isValid)
        #expect(result.errorMessage == nil)
    }

    @Test func completedByFieldWhitespaceOnlyFails() {
        let result = ValidationHelpers.validateRequired("   ", fieldName: "Completed by")
        #expect(!result.isValid)
        #expect(result.errorMessage == "Completed by is required")
    }
}

// MARK: - QA-002: JSON Unknown Fields Resilience
// Swift's JSONDecoder ignores unknown keys by default — these tests confirm that behaviour
// holds for WidgetDataManager.WidgetTask, which uses a custom CodingKeys enum.

struct JSONUnknownFieldsResilienceTests {

    @Test func widgetTaskDecodesWithExtraFields() throws {
        let json = """
        {
            "id": 1,
            "title": "Test",
            "description": null,
            "priority": "high",
            "in_progress": false,
            "due_date": null,
            "category": "test",
            "residence_name": "Home",
            "is_overdue": false,
            "is_due_within_7_days": false,
            "is_due_8_to_30_days": false,
            "unknown_field": "should be ignored",
            "another_unknown": 42
        }
        """.data(using: .utf8)!

        let task = try JSONDecoder().decode(WidgetDataManager.WidgetTask.self, from: json)
        #expect(task.id == 1)
        #expect(task.title == "Test")
        #expect(task.priority == "high")
        #expect(task.isOverdue == false)
        #expect(task.isDueWithin7Days == false)
        #expect(task.isDue8To30Days == false)
        #expect(task.residenceName == "Home")
    }

    @Test func widgetTaskIgnoresUnknownNestedObjects() throws {
        let json = """
        {
            "id": 99,
            "title": "Nested unknown test",
            "description": "Some description",
            "priority": "low",
            "in_progress": true,
            "due_date": "2026-03-01",
            "category": "plumbing",
            "residence_name": null,
            "is_overdue": true,
            "is_due_within_7_days": false,
            "is_due_8_to_30_days": false,
            "unknown_nested": {
                "key1": "value1",
                "key2": 123,
                "key3": true
            },
            "unknown_array": [1, 2, 3]
        }
        """.data(using: .utf8)!

        let task = try JSONDecoder().decode(WidgetDataManager.WidgetTask.self, from: json)
        #expect(task.id == 99)
        #expect(task.title == "Nested unknown test")
        #expect(task.description == "Some description")
        #expect(task.inProgress == true)
        #expect(task.dueDate == "2026-03-01")
        #expect(task.category == "plumbing")
        #expect(task.residenceName == nil)
        #expect(task.isOverdue == true)
    }
}
