import Foundation
import WidgetKit
import SwiftUI

// Daily Widget Reco：从 App Group 读取缓存；过期则请求后端 /v1/reco/widget；每日刷新（尽力而为）

private let appGroupSuiteName = "group.com.damer.mindfulness"
private let keyWidgetConfig = "widget.config.v1"
private let keyWidgetUserProfile = "widget.userProfile.v1_2"
private let keyWidgetDailyReco = "widget.dailyReco.v1"

private let fallbackTextTC = "你已经很努力了，今天也值得被温柔对待。"
private let fallbackTextEN = "You’ve been doing great — you deserve kindness today."

private func defaults() -> UserDefaults? {
    UserDefaults(suiteName: appGroupSuiteName)
}

private func isoNow() -> String {
    ISO8601DateFormatter().string(from: Date())
}

private func localDayKey(_ date: Date = Date()) -> String {
    let fmt = DateFormatter()
    fmt.calendar = Calendar.current
    fmt.timeZone = TimeZone.current
    fmt.dateFormat = "yyyy-MM-dd"
    return fmt.string(from: date)
}

private func resolveLang() -> String {
    // 仅支持 en/tc
    let preferred = Locale.preferredLanguages.first?.lowercased() ?? "en"
    return preferred.hasPrefix("zh") ? "tc" : "en"
}

private func resolveTitle(lang: String) -> String {
    lang == "en" ? "Mindfulness" : "正念"
}

private func resolveFooterHint(lang: String) -> String {
    lang == "en" ? "Tap to open the app" : "点我回到 App"
}

private func joinUrl(base: String, path: String) -> String {
    let b = base.trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: "/+$", with: "", options: .regularExpression)
    if path.hasPrefix("/") { return "\(b)\(path)" }
    return "\(b)/\(path)"
}

private func nextDailyRefreshDate(from now: Date) -> Date {
    // 下一天 00:10～01:00 之间随机一个时间点（用户本地时区）
    var cal = Calendar.current
    cal.timeZone = TimeZone.current
    guard let tomorrow = cal.date(byAdding: .day, value: 1, to: now) else {
        return now.addingTimeInterval(60 * 60 * 6)
    }
    let start = cal.startOfDay(for: tomorrow)
    let minDate = cal.date(byAdding: .minute, value: 10, to: start) ?? start.addingTimeInterval(60 * 10)
    let maxDate = cal.date(byAdding: .hour, value: 1, to: start) ?? start.addingTimeInterval(60 * 60)
    let interval = max(0, maxDate.timeIntervalSince(minDate))
    let jitter = interval > 0 ? Double.random(in: 0..<interval) : 0
    return minDate.addingTimeInterval(jitter)
}

private func readJsonDict(forKey key: String) -> [String: Any]? {
    guard let raw = defaults()?.string(forKey: key) else { return nil }
    guard let data = raw.data(using: .utf8) else { return nil }
    let obj = try? JSONSerialization.jsonObject(with: data, options: [])
    return obj as? [String: Any]
}

private func writeJsonDict(_ dict: [String: Any], forKey key: String) {
    guard let data = try? JSONSerialization.data(withJSONObject: dict, options: []) else { return }
    guard let raw = String(data: data, encoding: .utf8) else { return }
    defaults()?.set(raw, forKey: key)
}

private func readCachedText() -> (dayKey: String?, lang: String, text: String)? {
    guard let d = readJsonDict(forKey: keyWidgetDailyReco) else { return nil }
    let lang = (d["lang"] as? String) ?? resolveLang()
    let dayKey = d["day_key"] as? String
    if let item = d["item"] as? [String: Any], let text = item["text"] as? String, !text.isEmpty {
        return (dayKey: dayKey, lang: lang, text: text)
    }
    return nil
}

private func readApiBaseUrl() -> String? {
    guard let d = readJsonDict(forKey: keyWidgetConfig) else { return nil }
    let base = d["apiBaseUrl"] as? String
    return base?.trimmingCharacters(in: .whitespacesAndNewlines)
}

private func readUserProfileDict() -> [String: Any]? {
    guard let d = readJsonDict(forKey: keyWidgetUserProfile) else { return nil }
    return d["user_profile"] as? [String: Any]
}

private func saveDailyReco(lang: String, dayKey: String, contentId: Int, text: String, meta: [String: Any]?) {
    var dict: [String: Any] = [
        "schema_version": 1,
        "saved_at": isoNow(),
        "day_key": dayKey,
        "lang": lang,
        "source": "widget",
        "item": [
            "content_id": contentId,
            "text": text
        ]
    ]
    if let meta = meta { dict["meta"] = meta }
    writeJsonDict(dict, forKey: keyWidgetDailyReco)
}

private func fetchDailyRecoFromServer() async -> (lang: String, text: String, contentId: Int, meta: [String: Any]?)? {
    guard let baseUrl = readApiBaseUrl(), !baseUrl.isEmpty else { return nil }
    guard let userProfile = readUserProfileDict() else { return nil }

    let lang = resolveLang()
    let urlStr = joinUrl(base: baseUrl, path: "/v1/reco/widget")
    guard let url = URL(string: urlStr) else { return nil }

    var req = URLRequest(url: url)
    req.httpMethod = "POST"
    req.timeoutInterval = 12
    req.setValue("application/json", forHTTPHeaderField: "Content-Type")
    req.setValue(lang, forHTTPHeaderField: "Accept-Language")

    let body: [String: Any] = [
        "k": 1,
        "user_profile": userProfile,
        "already_recommended_ids": [],
        "touched_or_viewed_ids": []
    ]
    req.httpBody = try? JSONSerialization.data(withJSONObject: body, options: [])

    do {
        let (data, res) = try await URLSession.shared.data(for: req)
        guard let httpRes = res as? HTTPURLResponse, (200..<300).contains(httpRes.statusCode) else { return nil }

        let obj = try JSONSerialization.jsonObject(with: data, options: [])
        guard let root = obj as? [String: Any] else { return nil }
        guard let items = root["items"] as? [[String: Any]], let first = items.first else { return nil }
        guard let text = first["text"] as? String, !text.isEmpty else { return nil }
        let contentId = (first["content_id"] as? Int) ?? Int((first["content_id"] as? NSNumber)?.intValue ?? -1)
        if contentId < 0 { return nil }
        let meta = root["meta"] as? [String: Any]
        return (lang: lang, text: text, contentId: contentId, meta: meta)
    } catch {
        return nil
    }
}

struct EmotionProvider: TimelineProvider {
    func placeholder(in context: Context) -> EmotionEntry {
        let lang = resolveLang()
        return EmotionEntry(
            date: Date(),
            lang: lang,
            title: resolveTitle(lang: lang),
            text: lang == "en" ? fallbackTextEN : fallbackTextTC,
            footerHint: resolveFooterHint(lang: lang)
        )
    }

    func getSnapshot(in context: Context, completion: @escaping (EmotionEntry) -> ()) {
        completion(placeholder(in: context))
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<EmotionEntry>) -> ()) {
        Task {
            let lang = resolveLang()
            let today = localDayKey(Date())

            // 1) 今日缓存优先
            if let cached = readCachedText(), cached.dayKey == today {
                let entry = EmotionEntry(
                    date: Date(),
                    lang: cached.lang,
                    title: resolveTitle(lang: cached.lang),
                    text: cached.text,
                    footerHint: resolveFooterHint(lang: cached.lang)
                )
                completion(Timeline(entries: [entry], policy: .after(nextDailyRefreshDate(from: Date()))))
                return
            }

            // 2) 过期/缺失：尝试拉取后端
            if let fetched = await fetchDailyRecoFromServer() {
                saveDailyReco(lang: fetched.lang, dayKey: today, contentId: fetched.contentId, text: fetched.text, meta: fetched.meta)
                let entry = EmotionEntry(
                    date: Date(),
                    lang: fetched.lang,
                    title: resolveTitle(lang: fetched.lang),
                    text: fetched.text,
                    footerHint: resolveFooterHint(lang: fetched.lang)
                )
                completion(Timeline(entries: [entry], policy: .after(nextDailyRefreshDate(from: Date()))))
                return
            }

            // 3) 网络失败：用最近缓存或兜底
            if let cached = readCachedText() {
                let entry = EmotionEntry(
                    date: Date(),
                    lang: cached.lang,
                    title: resolveTitle(lang: cached.lang),
                    text: cached.text,
                    footerHint: resolveFooterHint(lang: cached.lang)
                )
                completion(Timeline(entries: [entry], policy: .after(nextDailyRefreshDate(from: Date()))))
                return
            }

            let entry = EmotionEntry(
                date: Date(),
                lang: lang,
                title: resolveTitle(lang: lang),
                text: lang == "en" ? fallbackTextEN : fallbackTextTC,
                footerHint: resolveFooterHint(lang: lang)
            )
            completion(Timeline(entries: [entry], policy: .after(nextDailyRefreshDate(from: Date()))))
        }
    }
}

struct EmotionEntry: TimelineEntry {
    let date: Date
    let lang: String
    let title: String
    let text: String
    let footerHint: String
}

struct EmotionWidgetView: View {
    var entry: EmotionProvider.Entry
    @Environment(\.widgetFamily) var family
    private let deepLink = URL(string: "client:///(app)/home")
    private let widgetBackgroundColor = Color(red: 1.0, green: 250.0 / 255.0, blue: 229.0 / 255.0) // #FFFAE5
    private let widgetTextColor = Color(red: 98.0 / 255.0, green: 59.0 / 255.0, blue: 59.0 / 255.0) // #623B3B

    var body: some View {
        // 只显示一句话（不显示标题/提示/时间等装饰元素）
        Text(entry.text)
            .font(fontForFamily())
            .foregroundColor(widgetTextColor)
            .multilineTextAlignment(.leading)
            .lineSpacing(lineSpacingForFamily())
            .lineLimit(lineLimitForFamily())
            .minimumScaleFactor(0.78)
            .padding(paddingForFamily())
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
            .widgetSolidBackground(widgetBackgroundColor)
            .widgetURL(deepLink)
    }

    private func fontForFamily() -> Font {
        switch family {
        case .systemSmall:
            return .system(size: 16, weight: .semibold)
        case .systemMedium:
            return .system(size: 18, weight: .semibold)
        case .systemLarge:
            return .system(size: 22, weight: .semibold)
        default:
            return .system(size: 16, weight: .semibold)
        }
    }

    private func lineSpacingForFamily() -> CGFloat {
        switch family {
        case .systemLarge:
            return 4
        default:
            return 3
        }
    }

    private func lineLimitForFamily() -> Int {
        switch family {
        case .systemSmall:
            return 5
        case .systemMedium:
            return 6
        case .systemLarge:
            return 8
        default:
            return 5
        }
    }

    private func paddingForFamily() -> CGFloat {
        switch family {
        case .systemSmall:
            return 14
        case .systemMedium:
            return 16
        case .systemLarge:
            return 18
        default:
            return 14
        }
    }
}

private struct WidgetSolidBackgroundModifier: ViewModifier {
    let color: Color

    func body(content: Content) -> some View {
        if #available(iOSApplicationExtension 17.0, *) {
            content.containerBackground(for: .widget) { color }
        } else {
            content
                .background(color)
                .ignoresSafeArea()
        }
    }
}

private extension View {
    func widgetSolidBackground(_ color: Color) -> some View {
        modifier(WidgetSolidBackgroundModifier(color: color))
    }
}

@main
struct EmotionWidget: Widget {
    let kind: String = "EmotionWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: EmotionProvider()) { entry in
            EmotionWidgetView(entry: entry)
        }
        .configurationDisplayName("情绪小组件")
        .description("一段温柔提醒，陪你回到当下。")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

