//
//  TaskMetricsTests.swift
//  honeyDueTests
//
//  Unit tests for WidgetDataManager.TaskMetrics and task categorization logic.
//

import Testing
import Foundation
@testable import honeyDue

// MARK: - Column Name Constants Tests

struct ColumnNameConstantsTests {

    @Test func overdueColumnName() {
        #expect(WidgetDataManager.overdueColumn == "overdue_tasks")
    }

    @Test func dueWithin7DaysColumnName() {
        #expect(WidgetDataManager.dueWithin7DaysColumn == "due_soon_tasks")
    }

    @Test func due8To30DaysColumnName() {
        #expect(WidgetDataManager.due8To30DaysColumn == "upcoming_tasks")
    }

    @Test func completedColumnName() {
        #expect(WidgetDataManager.completedColumn == "completed_tasks")
    }

    @Test func cancelledColumnName() {
        #expect(WidgetDataManager.cancelledColumn == "cancelled_tasks")
    }

    @Test func allColumnNamesUnique() {
        let columns = [
            WidgetDataManager.overdueColumn,
            WidgetDataManager.dueWithin7DaysColumn,
            WidgetDataManager.due8To30DaysColumn,
            WidgetDataManager.completedColumn,
            WidgetDataManager.cancelledColumn
        ]
        #expect(Set(columns).count == columns.count)
    }
}

// MARK: - TaskMetrics Calculation Tests

struct TaskMetricsCalculationTests {

    private func makeTask(
        id: Int,
        isOverdue: Bool = false,
        isDueWithin7Days: Bool = false,
        isDue8To30Days: Bool = false
    ) -> WidgetDataManager.WidgetTask {
        WidgetDataManager.WidgetTask(
            id: id,
            title: "Task \(id)",
            description: nil,
            priority: "medium",
            inProgress: false,
            dueDate: nil,
            category: "maintenance",
            residenceName: "Home",
            isOverdue: isOverdue,
            isDueWithin7Days: isDueWithin7Days,
            isDue8To30Days: isDue8To30Days
        )
    }

    @Test func emptyArrayReturnsZeroCounts() {
        let metrics = WidgetDataManager.calculateMetrics(from: [])
        #expect(metrics.totalCount == 0)
        #expect(metrics.overdueCount == 0)
        #expect(metrics.upcoming7Days == 0)
        #expect(metrics.upcoming30Days == 0)
    }

    @Test func allOverdueTasks() {
        let tasks = [
            makeTask(id: 1, isOverdue: true),
            makeTask(id: 2, isOverdue: true),
            makeTask(id: 3, isOverdue: true)
        ]
        let metrics = WidgetDataManager.calculateMetrics(from: tasks)
        #expect(metrics.totalCount == 3)
        #expect(metrics.overdueCount == 3)
        #expect(metrics.upcoming7Days == 0)
        #expect(metrics.upcoming30Days == 0)
    }

    @Test func allDueWithin7DaysTasks() {
        let tasks = [
            makeTask(id: 1, isDueWithin7Days: true),
            makeTask(id: 2, isDueWithin7Days: true),
            makeTask(id: 3, isDueWithin7Days: true),
            makeTask(id: 4, isDueWithin7Days: true)
        ]
        let metrics = WidgetDataManager.calculateMetrics(from: tasks)
        #expect(metrics.totalCount == 4)
        #expect(metrics.overdueCount == 0)
        #expect(metrics.upcoming7Days == 4)
        #expect(metrics.upcoming30Days == 0)
    }

    @Test func allDue8To30DaysTasks() {
        let tasks = [
            makeTask(id: 1, isDue8To30Days: true),
            makeTask(id: 2, isDue8To30Days: true)
        ]
        let metrics = WidgetDataManager.calculateMetrics(from: tasks)
        #expect(metrics.totalCount == 2)
        #expect(metrics.overdueCount == 0)
        #expect(metrics.upcoming7Days == 0)
        #expect(metrics.upcoming30Days == 2)
    }

    @Test func mixedTaskCategories() {
        let tasks = [
            makeTask(id: 1, isOverdue: true),
            makeTask(id: 2, isOverdue: true),
            makeTask(id: 3, isDueWithin7Days: true),
            makeTask(id: 4, isDueWithin7Days: true),
            makeTask(id: 5, isDueWithin7Days: true),
            makeTask(id: 6, isDue8To30Days: true)
        ]
        let metrics = WidgetDataManager.calculateMetrics(from: tasks)
        #expect(metrics.totalCount == 6)
        #expect(metrics.overdueCount == 2)
        #expect(metrics.upcoming7Days == 3)
        #expect(metrics.upcoming30Days == 1)
    }

    @Test func tasksWithNoFlagsCountedInTotalOnly() {
        let tasks = [
            makeTask(id: 1),
            makeTask(id: 2),
            makeTask(id: 3, isOverdue: true)
        ]
        let metrics = WidgetDataManager.calculateMetrics(from: tasks)
        #expect(metrics.totalCount == 3)
        #expect(metrics.overdueCount == 1)
        #expect(metrics.upcoming7Days == 0)
        #expect(metrics.upcoming30Days == 0)
    }

    @Test func singleTask() {
        let tasks = [makeTask(id: 1, isOverdue: true)]
        let metrics = WidgetDataManager.calculateMetrics(from: tasks)
        #expect(metrics.totalCount == 1)
        #expect(metrics.overdueCount == 1)
    }

    @Test func largeDataset() {
        var tasks: [WidgetDataManager.WidgetTask] = []
        for i in 1...50 { tasks.append(makeTask(id: i, isOverdue: true)) }
        for i in 51...150 { tasks.append(makeTask(id: i, isDueWithin7Days: true)) }
        for i in 151...225 { tasks.append(makeTask(id: i, isDue8To30Days: true)) }
        for i in 226...250 { tasks.append(makeTask(id: i)) }

        let metrics = WidgetDataManager.calculateMetrics(from: tasks)
        #expect(metrics.totalCount == 250)
        #expect(metrics.overdueCount == 50)
        #expect(metrics.upcoming7Days == 100)
        #expect(metrics.upcoming30Days == 75)
    }

    @Test func orderDoesNotAffectMetrics() {
        let tasks1 = [
            makeTask(id: 1, isOverdue: true),
            makeTask(id: 2, isDueWithin7Days: true),
            makeTask(id: 3, isDue8To30Days: true)
        ]
        let tasks2 = [
            makeTask(id: 3, isDue8To30Days: true),
            makeTask(id: 1, isOverdue: true),
            makeTask(id: 2, isDueWithin7Days: true)
        ]
        let m1 = WidgetDataManager.calculateMetrics(from: tasks1)
        let m2 = WidgetDataManager.calculateMetrics(from: tasks2)
        #expect(m1.totalCount == m2.totalCount)
        #expect(m1.overdueCount == m2.overdueCount)
        #expect(m1.upcoming7Days == m2.upcoming7Days)
        #expect(m1.upcoming30Days == m2.upcoming30Days)
    }
}

// MARK: - TaskMetrics Struct Tests

struct TaskMetricsStructTests {

    @Test func initializesWithCorrectValues() {
        let metrics = WidgetDataManager.TaskMetrics(
            totalCount: 10,
            overdueCount: 2,
            upcoming7Days: 3,
            upcoming30Days: 5
        )
        #expect(metrics.totalCount == 10)
        #expect(metrics.overdueCount == 2)
        #expect(metrics.upcoming7Days == 3)
        #expect(metrics.upcoming30Days == 5)
    }

    @Test func allowsZeroValues() {
        let metrics = WidgetDataManager.TaskMetrics(
            totalCount: 0,
            overdueCount: 0,
            upcoming7Days: 0,
            upcoming30Days: 0
        )
        #expect(metrics.totalCount == 0)
        #expect(metrics.overdueCount == 0)
        #expect(metrics.upcoming7Days == 0)
        #expect(metrics.upcoming30Days == 0)
    }
}

// MARK: - WidgetTask Codable Tests

struct WidgetTaskCodableTests {

    @Test func decodesFromJSON() throws {
        let json = """
        {
            "id": 123,
            "title": "Test Task",
            "description": "Desc",
            "priority": "high",
            "in_progress": true,
            "due_date": "2024-12-25",
            "category": "plumbing",
            "residence_name": "Home",
            "is_overdue": false,
            "is_due_within_7_days": true,
            "is_due_8_to_30_days": false
        }
        """.data(using: .utf8)!

        let task = try JSONDecoder().decode(WidgetDataManager.WidgetTask.self, from: json)
        #expect(task.id == 123)
        #expect(task.title == "Test Task")
        #expect(task.description == "Desc")
        #expect(task.priority == "high")
        #expect(task.inProgress == true)
        #expect(task.dueDate == "2024-12-25")
        #expect(task.category == "plumbing")
        #expect(task.residenceName == "Home")
        #expect(task.isOverdue == false)
        #expect(task.isDueWithin7Days == true)
        #expect(task.isDue8To30Days == false)
    }

    @Test func encodesToJSON() throws {
        let task = WidgetDataManager.WidgetTask(
            id: 456,
            title: "Encode Test",
            description: nil,
            priority: "medium",
            inProgress: false,
            dueDate: "2024-06-01",
            category: "hvac",
            residenceName: nil,
            isOverdue: true,
            isDueWithin7Days: false,
            isDue8To30Days: false
        )

        let data = try JSONEncoder().encode(task)
        let json = String(data: data, encoding: .utf8)!
        #expect(json.contains("\"id\":456"))
        #expect(json.contains("\"is_overdue\":true"))
        #expect(json.contains("\"in_progress\":false"))
    }

    @Test func roundTripsJSON() throws {
        let original = WidgetDataManager.WidgetTask(
            id: 789,
            title: "Round Trip",
            description: "Testing",
            priority: "low",
            inProgress: true,
            dueDate: "2024-03-15",
            category: "landscaping",
            residenceName: "Cabin",
            isOverdue: false,
            isDueWithin7Days: true,
            isDue8To30Days: false
        )

        let data = try JSONEncoder().encode(original)
        let decoded = try JSONDecoder().decode(WidgetDataManager.WidgetTask.self, from: data)

        #expect(decoded.id == original.id)
        #expect(decoded.title == original.title)
        #expect(decoded.description == original.description)
        #expect(decoded.inProgress == original.inProgress)
        #expect(decoded.isOverdue == original.isOverdue)
        #expect(decoded.isDueWithin7Days == original.isDueWithin7Days)
        #expect(decoded.isDue8To30Days == original.isDue8To30Days)
    }

    @Test func handlesNilOptionalFields() throws {
        let json = """
        {
            "id": 1,
            "title": "Minimal",
            "description": null,
            "priority": null,
            "in_progress": false,
            "due_date": null,
            "category": null,
            "residence_name": null,
            "is_overdue": false,
            "is_due_within_7_days": false,
            "is_due_8_to_30_days": false
        }
        """.data(using: .utf8)!

        let task = try JSONDecoder().decode(WidgetDataManager.WidgetTask.self, from: json)
        #expect(task.id == 1)
        #expect(task.description == nil)
        #expect(task.priority == nil)
        #expect(task.dueDate == nil)
        #expect(task.category == nil)
        #expect(task.residenceName == nil)
    }
}

// MARK: - Realistic Scenario Tests

struct RealisticScenarioTests {

    private func makeTask(
        id: Int,
        title: String,
        isOverdue: Bool = false,
        isDueWithin7Days: Bool = false,
        isDue8To30Days: Bool = false
    ) -> WidgetDataManager.WidgetTask {
        WidgetDataManager.WidgetTask(
            id: id,
            title: title,
            description: nil,
            priority: "medium",
            inProgress: false,
            dueDate: nil,
            category: "maintenance",
            residenceName: "Home",
            isOverdue: isOverdue,
            isDueWithin7Days: isDueWithin7Days,
            isDue8To30Days: isDue8To30Days
        )
    }

    @Test func newUserWithNoTasks() {
        let metrics = WidgetDataManager.calculateMetrics(from: [])
        #expect(metrics.totalCount == 0)
        #expect(metrics.overdueCount == 0)
    }

    @Test func userWithOverdueTasks() {
        let tasks = [
            makeTask(id: 1, title: "HVAC maintenance", isOverdue: true),
            makeTask(id: 2, title: "Roof inspection", isOverdue: true),
            makeTask(id: 3, title: "Weekly lawn care", isDueWithin7Days: true),
            makeTask(id: 4, title: "Monthly pest control", isDue8To30Days: true)
        ]
        let metrics = WidgetDataManager.calculateMetrics(from: tasks)
        #expect(metrics.totalCount == 4)
        #expect(metrics.overdueCount == 2)
        #expect(metrics.upcoming7Days == 1)
        #expect(metrics.upcoming30Days == 1)
    }

    @Test func typicalHomeowner() {
        let tasks = [
            makeTask(id: 1, title: "Replace water heater anode", isOverdue: true),
            makeTask(id: 2, title: "Mow lawn", isDueWithin7Days: true),
            makeTask(id: 3, title: "Take out trash", isDueWithin7Days: true),
            makeTask(id: 4, title: "Water plants", isDueWithin7Days: true),
            makeTask(id: 5, title: "Change air filter", isDue8To30Days: true),
            makeTask(id: 6, title: "Clean dryer vent", isDue8To30Days: true),
            makeTask(id: 7, title: "Organize garage"),
            makeTask(id: 8, title: "Paint fence")
        ]
        let metrics = WidgetDataManager.calculateMetrics(from: tasks)
        #expect(metrics.totalCount == 8)
        #expect(metrics.overdueCount == 1)
        #expect(metrics.upcoming7Days == 3)
        #expect(metrics.upcoming30Days == 2)
    }
}
