//! Shared application state managed by Tauri.

use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};

use tauri::{AppHandle, Manager, Wry};
use tokio::sync::{oneshot, Mutex as AsyncMutex};

use crate::agent::{AgentService, AgentSettingsService};
use crate::error::AppError;
use crate::news::NewsService;
use crate::portfolio::PortfolioService;
use crate::research::ResearchService;
use crate::terminal::google_finance::GoogleFinanceLookup;
use crate::terminal::sec_edgar::{
    LiveSecFetcher, SecEdgarClient, SecEdgarLookup, SecUserAgentProvider,
};
use crate::terminal::security_lookup::SecurityLookup;
use crate::terminal::TerminalCommandService;

pub struct PendingAgentToolApprovals {
    next_approval_id: AtomicU64,
    senders: Mutex<HashMap<String, oneshot::Sender<bool>>>,
}

impl PendingAgentToolApprovals {
    pub fn new() -> Self {
        Self {
            next_approval_id: AtomicU64::new(1),
            senders: Mutex::new(HashMap::new()),
        }
    }

    pub fn register(&self) -> Result<(String, oneshot::Receiver<bool>), AppError> {
        let approval_id = format!(
            "approval-{}",
            self.next_approval_id.fetch_add(1, Ordering::Relaxed)
        );
        let (sender, receiver) = oneshot::channel();
        let mut senders = self
            .senders
            .lock()
            .map_err(|_| AppError::InvalidSettings("approval state is unavailable".to_string()))?;
        senders.insert(approval_id.clone(), sender);
        Ok((approval_id, receiver))
    }

    pub fn resolve(&self, approval_id: &str, approved: bool) -> Result<(), AppError> {
        let sender = self
            .senders
            .lock()
            .map_err(|_| AppError::InvalidSettings("approval state is unavailable".to_string()))?
            .remove(approval_id)
            .ok_or_else(|| AppError::AgentToolApprovalNotFound(approval_id.to_string()))?;

        sender
            .send(approved)
            .map_err(|_| AppError::AgentToolApprovalNotFound(approval_id.to_string()))
    }

    pub fn cancel(&self, approval_id: &str) {
        if let Ok(mut senders) = self.senders.lock() {
            senders.remove(approval_id);
        }
    }
}

impl Default for PendingAgentToolApprovals {
    fn default() -> Self {
        Self::new()
    }
}

struct SettingsBackedSecUserAgentProvider {
    settings: AgentSettingsService<Wry>,
}

impl SecUserAgentProvider for SettingsBackedSecUserAgentProvider {
    fn user_agent(&self) -> Option<String> {
        self.settings
            .load()
            .ok()
            .and_then(|settings| {
                let value = settings.sec_edgar_user_agent.trim().to_string();
                if value.is_empty() {
                    None
                } else {
                    Some(value)
                }
            })
            .or_else(|| std::env::var("SEC_EDGAR_USER_AGENT").ok())
    }
}

/// Runtime services shared across Tauri commands.
pub struct AppState {
    /// Stateful chat service used for per-session conversation history and agent config.
    pub agent: AsyncMutex<AgentService<Wry>>,
    /// Local-first RSS/Atom news runtime backed by SQLite.
    pub news_service: Arc<NewsService>,
    /// Slash-command executor backed by shared services.
    pub command_service: Arc<TerminalCommandService>,
    /// Equity research note, graph, and ghost writer runtime.
    pub research_service: Arc<ResearchService<Wry>>,
    /// Pending approvals for agent-triggered mutating commands.
    pub pending_agent_tool_approvals: Arc<PendingAgentToolApprovals>,
}

impl AppState {
    /// Create a new application state for the current Tauri app.
    pub fn new(app_handle: &AppHandle<Wry>) -> Result<Self, AppError> {
        let sec_user_agent_provider = Arc::new(SettingsBackedSecUserAgentProvider {
            settings: AgentSettingsService::new(app_handle),
        });
        let security_lookup: Arc<dyn SecurityLookup> = Arc::new(GoogleFinanceLookup::default());
        let sec_edgar_lookup = Arc::new(SecEdgarLookup::new(Arc::new(SecEdgarClient::new(
            Box::new(LiveSecFetcher::new(sec_user_agent_provider)),
        ))));
        let portfolio_service =
            Arc::new(PortfolioService::new(app_handle, security_lookup.clone()));
        let news_root = app_handle
            .path()
            .app_data_dir()
            .map_err(|_| {
                AppError::InvalidSettings("news app data directory is unavailable".to_string())
            })?
            .join("news");
        let news_service = Arc::new(
            NewsService::new(
                news_root.join("news.sqlite"),
                news_root.join("news-feeds.json"),
                include_bytes!("../news-feeds.default.json"),
            )
            .map_err(|error| AppError::InvalidSettings(error.to_string()))?,
        );
        let research_root = app_handle
            .path()
            .app_data_dir()
            .map_err(|_| {
                AppError::InvalidSettings("research app data directory is unavailable".to_string())
            })?
            .join("research");
        let research_service = Arc::new(
            ResearchService::new(app_handle, research_root.join("research.sqlite"))
                .map_err(|error| AppError::InvalidSettings(error.to_string()))?,
        );

        Ok(Self {
            agent: AsyncMutex::new(AgentService::new(app_handle)?),
            news_service: news_service.clone(),
            command_service: Arc::new(TerminalCommandService::new(
                security_lookup,
                sec_edgar_lookup,
                portfolio_service,
                news_service,
            )),
            research_service,
            pending_agent_tool_approvals: Arc::new(PendingAgentToolApprovals::new()),
        })
    }
}
