package me.khodak.claudeusage

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import me.khodak.claudeusage.data.PreferencesManager
import me.khodak.claudeusage.data.UsageData
import me.khodak.claudeusage.databinding.ActivityMainBinding
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var prefs: PreferencesManager
    private lateinit var repo: UsageRepository

    private val notifPermLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { /* result handled silently */ }

    /** SAF: pick a destination file and write the current settings JSON to it. */
    private val exportLauncher =
        registerForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
            if (uri == null) return@registerForActivityResult
            try {
                contentResolver.openOutputStream(uri)?.use { it.write(prefs.exportSettings().toByteArray()) }
                toast("Settings exported")
            } catch (_: Exception) { toast("Export failed") }
        }

    /** SAF: pick a backup file and apply it. */
    private val importLauncher =
        registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
            if (uri == null) return@registerForActivityResult
            try {
                val json = contentResolver.openInputStream(uri)?.bufferedReader()?.use { it.readText() } ?: ""
                if (prefs.importSettings(json)) {
                    toast("Settings restored")
                    // Reflect restored prefs immediately: re-sync switches and redraw any widgets.
                    binding.switchNotify.isChecked = prefs.isNotifyEnabled()
                    binding.switchRingStyle.isChecked = prefs.getWidgetStyle() == PreferencesManager.STYLE_RINGS
                    binding.switchMaterialYou.isChecked = prefs.isMaterialYou()
                    ClaudeUsageWidget.notifyDataChanged(this)
                } else toast("Not a valid settings file")
            } catch (_: Exception) { toast("Import failed") }
        }

    /** Live refresh loop that runs only while the app is in the foreground. */
    private var autoRefreshJob: Job? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        prefs = PreferencesManager(this)
        repo = UsageRepository(prefs)

        binding.btnLogin.setOnClickListener {
            startActivity(Intent(this, LoginActivity::class.java))
        }

        // If already logged in, go straight to logged-in state without re-opening login
        if (prefs.isLoggedIn()) {
            updateUI(prefs.getUsageData())
        }

        binding.btnLogout.setOnClickListener {
            prefs.clearSession()
            updateUI(null)
        }

        binding.btnRefresh.setOnClickListener {
            refreshUsage()
        }

        binding.tvWidgetHint.setOnClickListener {
            startActivity(Intent(Intent.ACTION_MAIN).apply {
                addCategory(Intent.CATEGORY_HOME)
            })
        }

        setupNotificationSettings()
        setupWidgetStyleSetting()
        setupMaterialYouSetting()
        setupBackupRestore()

        binding.btnDebug.setOnClickListener {
            if (binding.tvDebugInfo.visibility == android.view.View.GONE) {
                binding.tvDebugInfo.text = repo.lastDebugInfo.ifBlank { "No debug info yet — tap Refresh first" }
                binding.tvDebugInfo.visibility = android.view.View.VISIBLE
                binding.btnDebug.text = "Hide API Debug"
            } else {
                binding.tvDebugInfo.visibility = android.view.View.GONE
                binding.btnDebug.text = "Show API Debug"
            }
        }
    }

    override fun onResume() {
        super.onResume()
        updateUI(prefs.getUsageData())  // show cached instantly — never a blank screen
        if (prefs.isLoggedIn()) startAutoRefresh()
    }

    override fun onPause() {
        super.onPause()
        autoRefreshJob?.cancel()
    }

    /** Refresh immediately on open, then every [REFRESH_INTERVAL_MS] while foregrounded. */
    private fun startAutoRefresh() {
        autoRefreshJob?.cancel()
        autoRefreshJob = lifecycleScope.launch {
            while (isActive) {
                doRefresh(silent = true)
                delay(REFRESH_INTERVAL_MS)
            }
        }
    }

    private fun setupNotificationSettings() {
        binding.switchNotify.isChecked = prefs.isNotifyEnabled()
        binding.switchNotify.setOnCheckedChangeListener { _, checked ->
            prefs.setNotifyEnabled(checked)
            if (checked) requestNotificationPermission()
        }
        // Alerts default on, so prompt for the runtime permission once on first launch
        // (a user who never toggles the switch would otherwise never be asked).
        if (prefs.isNotifyEnabled()) requestNotificationPermission()
    }

    /** Bars ⇄ Rings switch for the home-screen widget; redraws any placed widgets immediately. */
    private fun setupWidgetStyleSetting() {
        binding.switchRingStyle.isChecked = prefs.getWidgetStyle() == PreferencesManager.STYLE_RINGS
        binding.switchRingStyle.setOnCheckedChangeListener { _, checked ->
            prefs.setWidgetStyle(if (checked) PreferencesManager.STYLE_RINGS else PreferencesManager.STYLE_BARS)
            ClaudeUsageWidget.notifyDataChanged(this)
        }
    }

    /** Material You toggle — redraws placed widgets so the new accent applies immediately. */
    private fun setupMaterialYouSetting() {
        binding.switchMaterialYou.isChecked = prefs.isMaterialYou()
        binding.switchMaterialYou.setOnCheckedChangeListener { _, checked ->
            prefs.setMaterialYou(checked)
            ClaudeUsageWidget.notifyDataChanged(this)
            updateUI(prefs.getUsageData())   // refresh the in-app bars too
        }
    }

    private fun setupBackupRestore() {
        binding.btnExportSettings.setOnClickListener { exportLauncher.launch("claude-usage-settings.json") }
        binding.btnImportSettings.setOnClickListener { importLauncher.launch(arrayOf("application/json", "text/plain", "*/*")) }
    }

    private fun toast(msg: String) =
        android.widget.Toast.makeText(this, msg, android.widget.Toast.LENGTH_SHORT).show()

    private fun requestNotificationPermission() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
            != PackageManager.PERMISSION_GRANTED
        ) {
            notifPermLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
        }
    }

    /** Manual "Refresh Now" button — shows the spinner. */
    private fun refreshUsage() {
        lifecycleScope.launch { doRefresh(silent = false) }
    }

    private suspend fun doRefresh(silent: Boolean) {
        if (!silent) {
            binding.btnRefresh.isEnabled = false
            binding.progressIndicator.visibility = View.VISIBLE
        }
        val fresh = try {
            repo.fetchUsage()
        } catch (e: Exception) {
            if (e is kotlinx.coroutines.CancellationException) throw e
            UsageData(errorMessage = "Network error")
        }
        // Preserve last-good data so a failed/partial fetch never blanks the UI or widget.
        val merged = fresh.mergedWith(prefs.getUsageData())
        prefs.saveUsageData(merged)
        prefs.recordHistory(fresh)
        // Note: alerts fire only from the background worker, not here — no point pinging you
        // with a notification while you're already looking at the app.
        updateUI(merged)
        ClaudeUsageWidget.notifyDataChanged(this)  // opening the app refreshes the widget too
        if (binding.tvDebugInfo.visibility == View.VISIBLE) {
            binding.tvDebugInfo.text = repo.lastDebugInfo
        }
        if (!silent) {
            binding.btnRefresh.isEnabled = true
            binding.progressIndicator.visibility = View.GONE
        }
    }

    private fun updateUI(data: UsageData?) {
        val loggedIn = prefs.isLoggedIn()

        binding.layoutLoggedOut.visibility = if (loggedIn) View.GONE else View.VISIBLE
        binding.layoutLoggedIn.visibility = if (loggedIn) View.VISIBLE else View.GONE

        if (!loggedIn || data == null) return

        // ── Peak-hours row ───────────────────────────────────────────────────
        val peak = PeakHours.state()
        binding.imgPeak.setColorFilter(if (peak.active) PEAK_ON else PEAK_OFF)
        binding.tvPeak.setTextColor(if (peak.active) PEAK_ON else 0xFFAAAAAA.toInt())
        binding.tvPeak.text = if (peak.active)
            "🔥 Peak hours — ${peak.endsInLabel} · ${peak.windowLabel}"
        else
            "Off-peak · ${peak.windowLabel}"

        // ── Session (5-hour) bar — no pace marker ────────────────────────────
        binding.barSession.setImageBitmap(
            BarRenderer.render(data.progressPercent, null, SESSION_FILL, null)
        )

        // ── Weekly (7-day) bar — single-color pace marker ────────────────────
        if (data.weeklyUtilization >= 0f) {
            val wPct = data.weeklyUtilization.toInt()
            val weeklyPace = PaceCalc.compute(data.weeklyUtilization, data.weeklyResetAtEpoch, PaceCalc.WEEKLY_WINDOW_MS)
            binding.barWeekly.setImageBitmap(
                BarRenderer.render(wPct, weeklyPace?.markerPct, WEEKLY_FILL, if (weeklyPace != null) MARKER_COLOR else null)
            )
            binding.tvWeeklyUsage.text = "$wPct% this week"
        } else {
            binding.barWeekly.setImageBitmap(BarRenderer.render(0, null, WEEKLY_FILL, null))
            binding.tvWeeklyUsage.text = "—"
        }

        // Pace text removed per design — bars carry the signal.
        binding.tvSessionPace.visibility = View.GONE
        binding.tvWeeklyPace.visibility = View.GONE

        binding.tvUsage.text = when {
            data.fiveHourUtilization >= 0f -> {
                val pct = data.fiveHourUtilization.toInt()
                "$pct% of limit used"
            }
            data.messagesLimit > 0 && data.effectiveUsed >= 0 ->
                "${data.effectiveUsed} of ${data.messagesLimit} messages used"
            data.effectiveRemaining >= 0 && data.messagesLimit > 0 ->
                "${data.effectiveRemaining} of ${data.messagesLimit} remaining"
            data.effectiveRemaining >= 0 ->
                "${data.effectiveRemaining} messages remaining"
            else -> "Usage data unavailable"
        }

        binding.tvReset.text = formatReset(data.effectiveResetEpoch)
        binding.tvWeeklyReset.text = formatResetDay(data.weeklyResetAtEpoch)
        binding.tvUpdated.text = if (data.lastUpdated > 0)
            "Last updated: ${SimpleDateFormat("h:mm a, MMM d", Locale.US).format(Date(data.lastUpdated))}"
        else ""

        binding.tvError.text = data.errorMessage
        binding.tvError.visibility = if (data.errorMessage.isNotBlank()) View.VISIBLE else View.GONE

        binding.historyChart.setData(prefs.getHistory())
    }

    private fun formatReset(epochMs: Long): String {
        if (epochMs <= 0) return ""
        val now = System.currentTimeMillis()
        if (epochMs <= now) return "Resets soon"
        val diffH = TimeUnit.MILLISECONDS.toHours(epochMs - now)
        val timeStr = SimpleDateFormat("h:mm a", Locale.US).format(Date(epochMs))
        return when {
            diffH < 24 -> "Resets at $timeStr"
            diffH < 48 -> "Resets tomorrow $timeStr"
            else -> "Resets ${SimpleDateFormat("EEE", Locale.US).format(Date(epochMs))} $timeStr"
        }
    }

    /** Weekly reset shown with the weekday name ("Resets Friday 3:00 PM"), never "tomorrow". */
    private fun formatResetDay(epochMs: Long): String {
        if (epochMs <= 0) return ""
        if (epochMs <= System.currentTimeMillis()) return "Resets soon"
        val day = SimpleDateFormat("EEEE", Locale.US).format(Date(epochMs))
        val date = SimpleDateFormat("MMM d", Locale.US).format(Date(epochMs))
        val timeStr = SimpleDateFormat("h:mm a", Locale.US).format(Date(epochMs))
        return "Resets $day, $date · $timeStr"
    }

    companion object {
        private const val REFRESH_INTERVAL_MS = 30_000L   // live refresh cadence while app is open
        private const val SESSION_FILL = 0xFFCC785C.toInt()
        private const val WEEKLY_FILL = 0xFF7B8FCC.toInt()
        private const val MARKER_COLOR = 0xFFFFFFFF.toInt()   // single-color weekly pace marker
        private const val PEAK_ON = 0xFFCC785C.toInt()
        private const val PEAK_OFF = 0xFF666666.toInt()
    }
}
