import AppKit
import SwiftUI

/// macOS-standard tabbed Settings window. New per-provider sections (Codex,
/// Cursor, Copilot, etc.) plug in as additional tabs. Each tab owns its own
/// concerns; this top-level view only hosts the TabView shell.
struct SettingsView: View {
    @Environment(AppStore.self) private var store

    var body: some View {
        TabView(selection: Binding(get: { store.settingsTab }, set: { store.settingsTab = $0 })) {
            GeneralSettingsTab()
                .tabItem { Label("General", systemImage: "gearshape") }
                .tag("general")

            ClaudeSettingsTab()
                .tabItem { Label("Claude", systemImage: "brain") }
                .tag("claude")

            CodexSettingsTab()
                .tabItem { Label("Codex", systemImage: "chevron.left.forwardslash.chevron.right") }
                .tag("codex")

            DevinSettingsTab()
                .tabItem { Label("Devin", systemImage: "flame.fill") }
                .tag("devin")

            AboutSettingsTab()
                .tabItem { Label("About", systemImage: "info.circle") }
                .tag("about")
        }
        .frame(width: 520, height: 430)
    }
}

// MARK: - General

private struct GeneralSettingsTab: View {
    @Environment(AppStore.self) private var store

    // "Custom…" budget entry state, one per metric (cost in dollars, tokens in
    // millions). When custom is active the picker shows "Custom…" and a field
    // appears for an exact amount.
    @State private var costCustom = false
    @State private var tokenCustom = false
    @State private var costText = ""
    @State private var tokenText = ""

    // AppStorage (not a computed Binding over UsageRefreshCadence.current):
    // a plain UserDefaults write does not invalidate the view, so the picker
    // label would never reflect the selection even though the value landed.
    @AppStorage(UsageRefreshCadence.defaultsKey)
    private var usageRefreshSeconds: Int = UsageRefreshCadence.default.rawValue

    private let costPresets: Set<Double> = [25, 50, 100, 200, 500]
    private let tokenPresets: Set<Double> = [1_000_000, 5_000_000, 10_000_000, 25_000_000, 50_000_000, 100_000_000]

    private func applyCostBudget() {
        store.dailyBudget = max(0, Double(costText.trimmingCharacters(in: .whitespaces)) ?? 0)
    }

    private func applyTokenBudget() {
        let millions = Double(tokenText.trimmingCharacters(in: .whitespaces)) ?? 0
        store.dailyTokenBudget = max(0, millions * 1_000_000)
    }

    private func trimNumber(_ v: Double) -> String {
        v == v.rounded() ? String(Int(v)) : String(v)
    }

    // Help text under the budget picker. When "Custom…" is selected but no amount
    // has been entered, the budget is effectively 0 (off); call that out so the
    // alert does not look armed when it isn't.
    private var alertHelpText: String {
        let customEmpty = store.isTokenMetric
            ? (tokenCustom && store.dailyTokenBudget == 0)
            : (costCustom && store.dailyBudget == 0)
        if customEmpty { return "Enter an amount above, or the alert stays off." }
        return "Flame icon turns yellow when today's \(store.isTokenMetric ? "tokens" : "cost") pass the daily budget."
    }

    var body: some View {
        Form {
            Section("Display") {
                Picker("Currency", selection: Binding(
                    get: { store.currency },
                    set: { applyCurrency(code: $0) }
                )) {
                    ForEach(SupportedCurrency.allCases) { currency in
                        Text("\(currency.rawValue) — \(currency.displayName)").tag(currency.rawValue)
                    }
                }
                Picker("Metric", selection: Binding(
                    get: { store.displayMetric },
                    set: { store.displayMetric = $0 }
                )) {
                    Text("Cost ($)").tag(DisplayMetric.cost)
                    Text("Tokens (↑↓)").tag(DisplayMetric.tokens)
                    Text("Total Tokens").tag(DisplayMetric.totalTokens)
                    Text("Credits (Codex)").tag(DisplayMetric.credits)
                    Text("Icon Only").tag(DisplayMetric.iconOnly)
                }
                Picker("Period", selection: Binding(
                    get: { store.menubarPeriod },
                    set: { store.setMenubarPeriod($0) }
                )) {
                    ForEach(Period.menubarMetricCases) { period in
                        Text(period.menubarMetricLabel).tag(period)
                    }
                }
                .pickerStyle(.menu)
                Picker("Scope", selection: Binding(
                    get: { store.menubarScope },
                    set: { store.setMenubarScope($0) }
                )) {
                    ForEach(MenubarScope.allCases) { scope in
                        Text(scope.rawValue).tag(scope)
                    }
                }
                .pickerStyle(.menu)
                Picker("Accent", selection: Binding(
                    get: { store.accentPreset },
                    set: { store.accentPreset = $0 }
                )) {
                    ForEach(AccentPreset.allCases) { preset in
                        Text(preset.rawValue).tag(preset)
                    }
                }
            }

            Section("Usage Refresh") {
                Picker("Update every", selection: Binding(
                    get: { UsageRefreshCadence(rawValue: usageRefreshSeconds) ?? .default },
                    set: { usageRefreshSeconds = $0.rawValue }
                )) {
                    ForEach(UsageRefreshCadence.allCases) { cadence in
                        Text(cadence.label).tag(cadence)
                    }
                }
                .pickerStyle(.menu)
                Text("How often the menubar figure re-reads your local session data. Auto refreshes every 30 seconds while you're plugged in and backs off on battery; Manual only refreshes when you open the popover or click Refresh Now.")
                    .font(.system(size: 11))
                    .foregroundStyle(.secondary)
            }

            Section("Alerts") {
                // The budget tracks whatever the menubar metric shows: dollars for
                // the Cost metric, tokens for the Tokens / Total Tokens metrics.
                // "Custom…" reveals a field for an exact amount.
                if store.isTokenMetric {
                    Picker("Daily budget", selection: Binding(
                        get: { tokenCustom ? -1.0 : store.dailyTokenBudget },
                        set: { sel in
                            if sel < 0 {
                                tokenCustom = true
                                tokenText = store.dailyTokenBudget > 0 ? trimNumber(store.dailyTokenBudget / 1_000_000) : ""
                            } else {
                                tokenCustom = false
                                store.dailyTokenBudget = sel
                            }
                        }
                    )) {
                        Text("Off").tag(0.0)
                        Text("1M").tag(1_000_000.0)
                        Text("5M").tag(5_000_000.0)
                        Text("10M").tag(10_000_000.0)
                        Text("25M").tag(25_000_000.0)
                        Text("50M").tag(50_000_000.0)
                        Text("100M").tag(100_000_000.0)
                        Text("Custom…").tag(-1.0)
                    }
                    if tokenCustom {
                        HStack {
                            TextField("Amount", text: $tokenText)
                                .multilineTextAlignment(.trailing)
                                .onSubmit { applyTokenBudget() }
                                .onChange(of: tokenText) { _, _ in applyTokenBudget() }
                            Text("M tokens").foregroundStyle(.secondary)
                        }
                    }
                } else {
                    Picker("Daily budget", selection: Binding(
                        get: { costCustom ? -1.0 : store.dailyBudget },
                        set: { sel in
                            if sel < 0 {
                                costCustom = true
                                costText = store.dailyBudget > 0 ? trimNumber(store.dailyBudget) : ""
                            } else {
                                costCustom = false
                                store.dailyBudget = sel
                            }
                        }
                    )) {
                        Text("Off").tag(0.0)
                        Text("$25").tag(25.0)
                        Text("$50").tag(50.0)
                        Text("$100").tag(100.0)
                        Text("$200").tag(200.0)
                        Text("$500").tag(500.0)
                        Text("Custom…").tag(-1.0)
                    }
                    if costCustom {
                        HStack {
                            Text("$").foregroundStyle(.secondary)
                            TextField("Amount", text: $costText)
                                .multilineTextAlignment(.trailing)
                                .onSubmit { applyCostBudget() }
                                .onChange(of: costText) { _, _ in applyCostBudget() }
                        }
                    }
                }
                Text(alertHelpText)
                    .font(.system(size: 11))
                    .foregroundStyle(.secondary)
            }
            .onAppear {
                costCustom = store.dailyBudget > 0 && !costPresets.contains(store.dailyBudget)
                if costCustom { costText = trimNumber(store.dailyBudget) }
                tokenCustom = store.dailyTokenBudget > 0 && !tokenPresets.contains(store.dailyTokenBudget)
                if tokenCustom { tokenText = trimNumber(store.dailyTokenBudget / 1_000_000) }
            }
        }
        .formStyle(.grouped)
        .padding()
    }

    private func applyCurrency(code: String) {
        let symbol = CurrencyState.symbolForCode(code)
        Task {
            let cached = await FXRateCache.shared.cachedRate(for: code)
            if let cached {
                store.currency = code
                CurrencyState.shared.apply(code: code, rate: cached, symbol: symbol)
            }
            let fresh = await FXRateCache.shared.rate(for: code)
            store.currency = code
            CurrencyState.shared.apply(code: code, rate: fresh ?? cached, symbol: symbol)
        }
        CLICurrencyConfig.persist(code: code)
    }
}

// MARK: - Claude

private struct ClaudeSettingsTab: View {
    @Environment(AppStore.self) private var store

    var body: some View {
        Form {
            Section("Connection") {
                ClaudeConnectionRow()
            }
            Section {
                ClaudeConfigDirsSection()
            } header: {
                Text("Config Directories")
            } footer: {
                Text("Aggregate usage across multiple Claude config directories (e.g. work and personal accounts). Leave empty to track just the default `~/.claude`. The `CLAUDE_CONFIG_DIRS` environment variable, if set, overrides this list.")
                    .font(.system(size: 11))
                    .foregroundStyle(.secondary)
            }
            Section("Quota Refresh") {
                Picker("Update every", selection: Binding(
                    get: { SubscriptionRefreshCadence.current },
                    set: { SubscriptionRefreshCadence.current = $0 }
                )) {
                    ForEach(SubscriptionRefreshCadence.allCases) { cadence in
                        Text(cadence.label).tag(cadence)
                    }
                }
                .pickerStyle(.menu)
                Text("Anthropic rate-limits this endpoint per account. 2 minutes is plenty for the 5-hour and weekly windows; pick Manual if you only want updates on demand.")
                    .font(.system(size: 11))
                    .foregroundStyle(.secondary)
                Button("Refresh Now") {
                    Task { await store.refreshSubscription() }
                }
            }
        }
        .formStyle(.grouped)
        .padding()
    }
}

private struct ClaudeConnectionRow: View {
    @Environment(AppStore.self) private var store
    @State private var showDisconnectConfirm = false

    var body: some View {
        HStack(alignment: .top, spacing: 12) {
            Image(systemName: stateIcon)
                .font(.system(size: 18))
                .foregroundStyle(stateTint)
                .frame(width: 22)
            VStack(alignment: .leading, spacing: 2) {
                Text(stateTitle)
                    .font(.system(size: 12, weight: .semibold))
                Text(stateDetail)
                    .font(.system(size: 11))
                    .foregroundStyle(.secondary)
                    .lineLimit(2)
            }
            Spacer()
            actionButton
        }
        .padding(.vertical, 4)
    }

    private var stateIcon: String {
        switch store.subscriptionLoadState {
        case .loaded: return "checkmark.circle.fill"
        case .terminalFailure: return "exclamationmark.triangle.fill"
        case .transientFailure: return "clock.arrow.circlepath"
        case .bootstrapping, .loading: return "ellipsis.circle"
        case .notBootstrapped, .dormant, .noCredentials: return "link.circle"
        case .failed: return "xmark.circle"
        }
    }

    private var stateTint: Color {
        switch store.subscriptionLoadState {
        case .loaded: return .green
        case .terminalFailure, .failed: return .red
        case .transientFailure: return .orange
        default: return .secondary
        }
    }

    private var stateTitle: String {
        switch store.subscriptionLoadState {
        case .loaded: return "Connected"
        case let .terminalFailure(reason): return reason ?? "Reconnect required"
        case .transientFailure: return "Backing off"
        case .bootstrapping: return "Connecting…"
        case .loading: return "Refreshing…"
        case .dormant: return "Ready"
        case .notBootstrapped, .noCredentials: return "Not connected"
        case .failed: return "Couldn't load plan data"
        }
    }

    private var stateDetail: String {
        switch store.subscriptionLoadState {
        case .loaded:
            if let tier = store.subscription?.tier.displayName {
                return "Plan: \(tier)"
            }
            return "Live quota tracked from Anthropic."
        case .terminalFailure: return "Open Claude Code in your terminal and type `/login`, then click Reconnect."
        case .transientFailure: return store.subscriptionError ?? "Anthropic rate-limited; auto-retrying."
        case .bootstrapping: return "macOS may ask permission to read your credentials."
        case .loading: return "Background refresh in progress."
        case .dormant: return "Tap Load Quota to fetch live usage from Anthropic."
        case .notBootstrapped, .noCredentials: return "Click Connect to read your Claude Code credentials and start tracking quota."
        case .failed: return store.subscriptionError ?? ""
        }
    }

    @ViewBuilder
    private var actionButton: some View {
        switch store.subscriptionLoadState {
        case .loaded, .transientFailure, .loading:
            Button("Disconnect") { showDisconnectConfirm = true }
                .confirmationDialog(
                    "Disconnect Claude?",
                    isPresented: $showDisconnectConfirm
                ) {
                    Button("Disconnect", role: .destructive) {
                        store.disconnectSubscription()
                    }
                    Button("Cancel", role: .cancel) {}
                } message: {
                    Text("CodeBurn will stop tracking quota and delete its local copy of your Claude credentials. Your Claude Code keychain entry is untouched — Claude Code keeps working.")
                }
        case .terminalFailure, .noCredentials, .failed:
            Button("Reconnect") { Task { await store.bootstrapSubscription() } }
                .buttonStyle(.borderedProminent)
        case .dormant:
            Button("Load Quota") { Task { await store.activateClaudeFromDormant() } }
                .buttonStyle(.borderedProminent)
        case .notBootstrapped:
            Button("Connect") { Task { await store.bootstrapSubscription() } }
                .buttonStyle(.borderedProminent)
        case .bootstrapping:
            ProgressView().controlSize(.small)
        }
    }
}

// MARK: - Claude config directories

private struct ClaudeConfigDirsSection: View {
    @Environment(AppStore.self) private var store
    @State private var dirs: [String] = CLIClaudeConfig.load()

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            if dirs.isEmpty {
                Text("No extra directories — tracking the default `~/.claude`.")
                    .font(.system(size: 11))
                    .foregroundStyle(.secondary)
            } else {
                ForEach(Array(dirs.enumerated()), id: \.offset) { index, dir in
                    HStack(spacing: 8) {
                        Image(systemName: "folder")
                            .font(.system(size: 12))
                            .foregroundStyle(.secondary)
                        Text(dir)
                            .font(.system(size: 12))
                            .truncationMode(.middle)
                            .lineLimit(1)
                            .help(dir)
                        Spacer()
                        Button {
                            remove(at: index)
                        } label: {
                            Image(systemName: "minus.circle.fill")
                                .foregroundStyle(.secondary)
                        }
                        .buttonStyle(.plain)
                        .help("Remove")
                    }
                }
            }

            Button {
                addDirectory()
            } label: {
                Label("Add Directory…", systemImage: "plus")
            }
            .controlSize(.small)
        }
        .padding(.vertical, 2)
    }

    private func addDirectory() {
        let panel = NSOpenPanel()
        panel.canChooseDirectories = true
        panel.canChooseFiles = false
        panel.allowsMultipleSelection = true
        panel.prompt = "Add"
        panel.message = "Choose one or more Claude config directories (each containing a `projects` folder)."
        guard panel.runModal() == .OK else { return }

        let added = panel.urls.map { $0.path }
        var next = dirs
        for path in added where !next.contains(path) {
            next.append(path)
        }
        apply(next)
    }

    private func remove(at index: Int) {
        guard dirs.indices.contains(index) else { return }
        var next = dirs
        next.remove(at: index)
        apply(next)
    }

    /// Persists the new list and kicks a forced refresh so the dashboard
    /// reflects the changed aggregation immediately.
    private func apply(_ next: [String]) {
        dirs = next
        CLIClaudeConfig.persist(dirs: next)
        Task { await store.refresh(includeOptimize: false, force: true, showLoading: true) }
    }
}

// MARK: - Codex

private struct CodexSettingsTab: View {
    @Environment(AppStore.self) private var store

    var body: some View {
        Form {
            Section("Connection") {
                CodexConnectionRow()
            }
            Section {
                Text("Codex live-quota tracking reads `~/.codex/auth.json` once on Connect, then keeps a local copy under Application Support so subsequent quota fetches don't re-read the original. Only ChatGPT-mode auth (Plus / Pro / Team / Business) is supported — API-key users are billed per request and have a different reporting surface.")
                    .font(.system(size: 11))
                    .foregroundStyle(.secondary)
            } header: {
                Text("How it works")
            }
        }
        .formStyle(.grouped)
        .padding()
    }
}

private struct CodexConnectionRow: View {
    @Environment(AppStore.self) private var store
    @State private var showDisconnectConfirm = false

    var body: some View {
        HStack(alignment: .top, spacing: 12) {
            Image(systemName: stateIcon)
                .font(.system(size: 18))
                .foregroundStyle(stateTint)
                .frame(width: 22)
            VStack(alignment: .leading, spacing: 2) {
                Text(stateTitle)
                    .font(.system(size: 12, weight: .semibold))
                Text(stateDetail)
                    .font(.system(size: 11))
                    .foregroundStyle(.secondary)
                    .lineLimit(2)
            }
            Spacer()
            actionButton
        }
        .padding(.vertical, 4)
    }

    private var stateIcon: String {
        switch store.codexLoadState {
        case .loaded: return "checkmark.circle.fill"
        case .terminalFailure: return "exclamationmark.triangle.fill"
        case .transientFailure: return "clock.arrow.circlepath"
        case .bootstrapping, .loading: return "ellipsis.circle"
        case .notBootstrapped, .dormant, .noCredentials: return "link.circle"
        case .failed: return "xmark.circle"
        }
    }

    private var stateTint: Color {
        switch store.codexLoadState {
        case .loaded: return .green
        case .terminalFailure, .failed: return .red
        case .transientFailure: return .orange
        default: return .secondary
        }
    }

    private var stateTitle: String {
        switch store.codexLoadState {
        case .loaded: return "Connected"
        case let .terminalFailure(reason): return reason ?? "Reconnect required"
        case .transientFailure: return "Backing off"
        case .bootstrapping: return "Connecting…"
        case .loading: return "Refreshing…"
        case .dormant: return "Ready"
        case .notBootstrapped, .noCredentials: return "Not connected"
        case .failed: return "Couldn't load Codex quota"
        }
    }

    private var stateDetail: String {
        switch store.codexLoadState {
        case .loaded:
            if let plan = store.codexUsage?.plan.displayName {
                return "Plan: \(plan)"
            }
            return "Live quota tracked from chatgpt.com."
        case .terminalFailure:
            // Be specific about the cause: the message we already surface in
            // codexError will say "API-key mode" if that's the situation, so
            // the generic "run codex login" hint covers both cases.
            if let err = store.codexError, err.lowercased().contains("api-key") {
                return "Codex is in API-key mode. Run `codex login` and choose a ChatGPT plan to enable quota tracking."
            }
            return "Run `codex login` in your terminal to sign in again, then click Reconnect."
        case .transientFailure: return store.codexError ?? "ChatGPT rate-limited; auto-retrying."
        case .bootstrapping: return "Reading ~/.codex/auth.json."
        case .loading: return "Background refresh in progress."
        case .dormant: return "Tap Load Quota to fetch live usage from chatgpt.com."
        case .notBootstrapped, .noCredentials:
            return "Click Connect to read your Codex CLI credentials. If Connect fails, run `codex login` in your terminal first to create ~/.codex/auth.json."
        case .failed: return store.codexError ?? ""
        }
    }

    @ViewBuilder
    private var actionButton: some View {
        switch store.codexLoadState {
        case .loaded, .transientFailure, .loading:
            Button("Disconnect") { showDisconnectConfirm = true }
                .confirmationDialog(
                    "Disconnect Codex?",
                    isPresented: $showDisconnectConfirm
                ) {
                    Button("Disconnect", role: .destructive) {
                        store.disconnectCodex()
                    }
                    Button("Cancel", role: .cancel) {}
                } message: {
                    Text("CodeBurn will stop tracking quota and delete its local copy of your Codex credentials. Your ~/.codex/auth.json is untouched — Codex CLI keeps working.")
                }
        case .terminalFailure, .noCredentials, .failed:
            Button("Reconnect") { Task { await store.bootstrapCodex() } }
                .buttonStyle(.borderedProminent)
        case .dormant:
            Button("Load Quota") { Task { await store.activateCodexFromDormant() } }
                .buttonStyle(.borderedProminent)
        case .notBootstrapped:
            Button("Connect") { Task { await store.bootstrapCodex() } }
                .buttonStyle(.borderedProminent)
        case .bootstrapping:
            ProgressView().controlSize(.small)
        }
    }
}

// MARK: - Devin

private struct DevinSettingsTab: View {
    @State private var rateText: String = ""
    @State private var statusText: String = ""

    private var parsedRate: Double? {
        let trimmed = rateText.trimmingCharacters(in: .whitespacesAndNewlines)
        guard let value = Double(trimmed), value.isFinite, value > 0 else { return nil }
        return value
    }

    var body: some View {
        Form {
            Section("ACU Conversion") {
                HStack(alignment: .center, spacing: 10) {
                    Text("USD per ACU")
                    Spacer()
                    TextField("", text: $rateText)
                        .textFieldStyle(.roundedBorder)
                        .multilineTextAlignment(.trailing)
                        .frame(width: 96)
                        .accessibilityLabel("USD per ACU")
                    Text("USD")
                        .foregroundStyle(.secondary)
                        .frame(width: 36, alignment: .leading)
                }

                Button("Save") {
                    saveRate()
                }
                .buttonStyle(.borderedProminent)
                .disabled(parsedRate == nil)

                if !statusText.isEmpty {
                    Text(statusText)
                        .font(.system(size: 11))
                        .foregroundStyle(.secondary)
                }
            }

            Section {
                Text("CodeBurn reads Devin ACU usage from local transcripts only after this rate is configured, then multiplies each step by the rate before reporting cost.")
                    .font(.system(size: 11))
                    .foregroundStyle(.secondary)
            } header: {
                Text("How it works")
            }
        }
        .formStyle(.grouped)
        .padding()
        .onAppear {
            if let rate = CLIDevinConfig.loadAcuUsdRate() {
                rateText = Self.format(rate)
            }
        }
    }

    private func saveRate() {
        guard let rate = parsedRate else { return }
        CLIDevinConfig.persistAcuUsdRate(rate)
        rateText = Self.format(rate)
        statusText = "Saved. Refresh CodeBurn to recalculate Devin cost."
    }

    private static func format(_ value: Double) -> String {
        let formatter = NumberFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.numberStyle = .decimal
        formatter.minimumFractionDigits = 0
        formatter.maximumFractionDigits = 6
        return formatter.string(from: NSNumber(value: value)) ?? String(value)
    }
}

// MARK: - About

private struct AboutSettingsTab: View {
    private let appVersion: String = AppVersion.normalizedBundleShortVersion
    private let buildVersion: String = AppVersion.normalizedBundleBuildVersion

    var body: some View {
        VStack(spacing: 14) {
            Image(systemName: "flame.fill")
                .font(.system(size: 40))
                .foregroundStyle(Theme.brandAccent)
            Text("CodeBurn")
                .font(.system(size: 18, weight: .semibold))
            Text("AI Coding Cost Tracker")
                .font(.system(size: 12))
                .foregroundStyle(.secondary)
            Text("Version \(appVersion) (\(buildVersion))")
                .font(.codeMono(size: 11))
                .foregroundStyle(.secondary)
            Link(destination: URL(string: "https://github.com/getagentseal/codeburn")!) {
                Label("Star on GitHub", systemImage: "star.fill")
                    .font(.system(size: 12, weight: .medium))
            }
            .buttonStyle(.borderedProminent)
            .tint(Theme.brandAccent)
            HStack(spacing: 10) {
                Link("GitHub", destination: URL(string: "https://github.com/getagentseal/codeburn")!)
                Link("Issues", destination: URL(string: "https://github.com/getagentseal/codeburn/issues")!)
                Link("Sponsor", destination: URL(string: "https://github.com/sponsors/iamtoruk")!)
            }
            .font(.system(size: 12))
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .padding()
    }
}
