import Foundation

struct FoodEntry: Codable, Identifiable, Hashable {
    let id: String
    let foodName: String
    let servingDescription: String?
    let quantity: Double
    let unit: String
    let mealType: String
    let calories: Double
    let protein: Double
    let carbs: Double
    let fat: Double
    let sugar: Double?
    let fiber: Double?
    let entryType: String?
    let method: String?
    let foodId: String?
    let imageUrl: String?
    let note: String?
    let loggedAt: String?

    enum CodingKeys: String, CodingKey {
        case id, quantity, unit, calories, protein, carbs, fat, sugar, fiber, note, method
        case foodName = "food_name"
        case servingDescription = "serving_description"
        case mealType = "meal_type"
        case entryType = "entry_type"
        case foodId = "food_id"
        case imageUrl = "image_url"
        case loggedAt = "logged_at"
    }

    /// Alternate keys from the snapshot-based API response
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: FlexibleCodingKeys.self)

        id = try container.decode(String.self, forKey: .init("id"))
        quantity = try container.decodeIfPresent(Double.self, forKey: .init("quantity")) ?? 1
        unit = try container.decodeIfPresent(String.self, forKey: .init("unit")) ?? "serving"
        mealType = try container.decodeIfPresent(String.self, forKey: .init("meal_type")) ?? "snack"

        // Handle both "food_name" and "snapshot_food_name"
        if let name = try container.decodeIfPresent(String.self, forKey: .init("food_name")) {
            foodName = name
        } else if let name = try container.decodeIfPresent(String.self, forKey: .init("snapshot_food_name")) {
            foodName = name
        } else {
            foodName = "Unknown"
        }

        servingDescription = try container.decodeIfPresent(String.self, forKey: .init("serving_description"))
            ?? container.decodeIfPresent(String.self, forKey: .init("snapshot_serving_label"))

        // Handle both direct and snapshot_ prefixed fields
        calories = try container.decodeIfPresent(Double.self, forKey: .init("calories"))
            ?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_calories"))
            ?? 0
        protein = try container.decodeIfPresent(Double.self, forKey: .init("protein"))
            ?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_protein"))
            ?? 0
        carbs = try container.decodeIfPresent(Double.self, forKey: .init("carbs"))
            ?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_carbs"))
            ?? 0
        fat = try container.decodeIfPresent(Double.self, forKey: .init("fat"))
            ?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_fat"))
            ?? 0
        sugar = try container.decodeIfPresent(Double.self, forKey: .init("sugar"))
            ?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_sugar"))
        fiber = try container.decodeIfPresent(Double.self, forKey: .init("fiber"))
            ?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_fiber"))

        entryType = try container.decodeIfPresent(String.self, forKey: .init("entry_type"))
        method = try container.decodeIfPresent(String.self, forKey: .init("entry_method"))
            ?? container.decodeIfPresent(String.self, forKey: .init("method"))
        foodId = try container.decodeIfPresent(String.self, forKey: .init("food_id"))
        imageUrl = try container.decodeIfPresent(String.self, forKey: .init("image_url"))
            ?? container.decodeIfPresent(String.self, forKey: .init("food_image_path"))
        note = try container.decodeIfPresent(String.self, forKey: .init("note"))
        loggedAt = try container.decodeIfPresent(String.self, forKey: .init("logged_at"))
            ?? container.decodeIfPresent(String.self, forKey: .init("created_at"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(foodName, forKey: .foodName)
        try container.encodeIfPresent(servingDescription, forKey: .servingDescription)
        try container.encode(quantity, forKey: .quantity)
        try container.encode(unit, forKey: .unit)
        try container.encode(mealType, forKey: .mealType)
        try container.encode(calories, forKey: .calories)
        try container.encode(protein, forKey: .protein)
        try container.encode(carbs, forKey: .carbs)
        try container.encode(fat, forKey: .fat)
        try container.encodeIfPresent(sugar, forKey: .sugar)
        try container.encodeIfPresent(fiber, forKey: .fiber)
        try container.encodeIfPresent(entryType, forKey: .entryType)
        try container.encodeIfPresent(method, forKey: .method)
        try container.encodeIfPresent(foodId, forKey: .foodId)
        try container.encodeIfPresent(imageUrl, forKey: .imageUrl)
        try container.encodeIfPresent(note, forKey: .note)
        try container.encodeIfPresent(loggedAt, forKey: .loggedAt)
    }

    static func == (lhs: FoodEntry, rhs: FoodEntry) -> Bool {
        lhs.id == rhs.id
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

/// Flexible coding keys for handling multiple API shapes
struct FlexibleCodingKeys: CodingKey {
    var stringValue: String
    var intValue: Int?

    init(_ string: String) {
        self.stringValue = string
        self.intValue = nil
    }

    init?(stringValue: String) {
        self.stringValue = stringValue
        self.intValue = nil
    }

    init?(intValue: Int) {
        self.stringValue = "\(intValue)"
        self.intValue = intValue
    }
}

struct FoodItem: Codable, Identifiable {
    let id: String
    let name: String
    let brand: String?
    let baseUnit: String?
    let caloriesPerBase: Double
    let proteinPerBase: Double?
    let carbsPerBase: Double?
    let fatPerBase: Double?
    let sugarPerBase: Double?
    let fiberPerBase: Double?
    let status: String?
    let imageUrl: String?
    let favorite: Bool?

    enum CodingKeys: String, CodingKey {
        case id, name, brand, status, favorite
        case baseUnit = "base_unit"
        case caloriesPerBase = "calories_per_base"
        case proteinPerBase = "protein_per_base"
        case carbsPerBase = "carbs_per_base"
        case fatPerBase = "fat_per_base"
        case sugarPerBase = "sugar_per_base"
        case fiberPerBase = "fiber_per_base"
        case imageUrl = "image_url"
    }

    var displayUnit: String {
        baseUnit ?? "serving"
    }

    var displayInfo: String {
        let parts = [brand].compactMap { $0 }
        let prefix = parts.isEmpty ? "" : "\(parts.joined(separator: " ")) - "
        return "\(prefix)\(displayUnit)"
    }

    func scaledCalories(quantity: Double) -> Double {
        caloriesPerBase * quantity
    }

    func scaledProtein(quantity: Double) -> Double {
        (proteinPerBase ?? 0) * quantity
    }

    func scaledCarbs(quantity: Double) -> Double {
        (carbsPerBase ?? 0) * quantity
    }

    func scaledFat(quantity: Double) -> Double {
        (fatPerBase ?? 0) * quantity
    }
}

struct DailyGoal: Codable {
    let calories: Double
    let protein: Double
    let carbs: Double
    let fat: Double
    let sugar: Double?
    let fiber: Double?

    static let defaultGoal = DailyGoal(
        calories: 2000, protein: 150, carbs: 200, fat: 65, sugar: 50, fiber: 30
    )
}

struct MealTemplate: Codable, Identifiable {
    let id: String
    let name: String
    let mealType: String
    let calories: Double
    let protein: Double?
    let carbs: Double?
    let fat: Double?
    let itemsCount: Int?

    // Support flexible decoding for templates with nested items
    enum CodingKeys: String, CodingKey {
        case id, name, calories, protein, carbs, fat, items
        case mealType = "meal_type"
        case itemsCount = "items_count"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        mealType = try container.decodeIfPresent(String.self, forKey: .mealType) ?? "snack"
        protein = try container.decodeIfPresent(Double.self, forKey: .protein)
        carbs = try container.decodeIfPresent(Double.self, forKey: .carbs)
        fat = try container.decodeIfPresent(Double.self, forKey: .fat)

        // Try direct calories first, then compute from items
        if let directCals = try? container.decode(Double.self, forKey: .calories) {
            calories = directCals
            itemsCount = try container.decodeIfPresent(Int.self, forKey: .itemsCount)
        } else if let items = try? container.decode([TemplateItem].self, forKey: .items) {
            calories = items.reduce(0) { $0 + ($1.snapshotCalories ?? 0) * ($1.quantity ?? 1) }
            itemsCount = items.count
        } else {
            calories = 0
            itemsCount = try container.decodeIfPresent(Int.self, forKey: .itemsCount)
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
        try container.encode(mealType, forKey: .mealType)
        try container.encode(calories, forKey: .calories)
        try container.encodeIfPresent(protein, forKey: .protein)
        try container.encodeIfPresent(carbs, forKey: .carbs)
        try container.encodeIfPresent(fat, forKey: .fat)
        try container.encodeIfPresent(itemsCount, forKey: .itemsCount)
    }
}

private struct TemplateItem: Codable {
    let snapshotCalories: Double?
    let quantity: Double?

    enum CodingKeys: String, CodingKey {
        case snapshotCalories = "snapshot_calories"
        case quantity
    }
}

struct CreateEntryRequest: Encodable {
    let foodId: String
    let quantity: Double
    let unit: String
    let mealType: String
    let entryDate: String
    let entryMethod: String
    let source: String

    enum CodingKeys: String, CodingKey {
        case quantity, unit, source
        case foodId = "food_id"
        case mealType = "meal_type"
        case entryDate = "entry_date"
        case entryMethod = "entry_method"
    }
}

struct UpdateEntryRequest: Encodable {
    var quantity: Double?
    var unit: String?
    var mealType: String?

    enum CodingKeys: String, CodingKey {
        case quantity, unit
        case mealType = "meal_type"
    }
}

struct SplitFoodRequest: Encodable {
    let text: String
    let mealType: String

    enum CodingKeys: String, CodingKey {
        case text
        case mealType = "meal_type"
    }
}

// Meals enum for type safety
enum MealType: String, CaseIterable, Identifiable {
    case breakfast
    case lunch
    case dinner
    case snack

    var id: String { rawValue }

    var displayName: String {
        rawValue.capitalized
    }

    var icon: String {
        switch self {
        case .breakfast: return "sunrise.fill"
        case .lunch: return "sun.max.fill"
        case .dinner: return "moon.fill"
        case .snack: return "leaf.fill"
        }
    }

    static func guess() -> MealType {
        let hour = Calendar.current.component(.hour, from: Date())
        switch hour {
        case 5..<11: return .breakfast
        case 11..<15: return .lunch
        case 15..<20: return .dinner
        default: return .snack
        }
    }
}

// Helper for grouping entries by meal
struct MealGroup: Identifiable {
    let meal: MealType
    let entries: [FoodEntry]

    var id: String { meal.rawValue }

    var totalCalories: Double {
        entries.reduce(0) { $0 + $1.calories }
    }

    var totalProtein: Double {
        entries.reduce(0) { $0 + $1.protein }
    }

    var totalCarbs: Double {
        entries.reduce(0) { $0 + $1.carbs }
    }

    var totalFat: Double {
        entries.reduce(0) { $0 + $1.fat }
    }
}
