use std::collections::HashMap;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;

use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AccountInfo {
    pub email: String,
    pub uuid: String,
    pub added_at: DateTime<Utc>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Store {
    pub active: Option<u32>,
    /// Keyed by account number as string (JSON object keys must be strings).
    pub accounts: HashMap<String, AccountInfo>,
    /// Ordered list of account numbers — controls display order.
    pub sequence: Vec<u32>,
}

impl Store {
    pub fn new() -> Self {
        Store {
            active: None,
            accounts: HashMap::new(),
            sequence: Vec::new(),
        }
    }

    pub fn next_id(&self) -> u32 {
        self.sequence.iter().copied().max().unwrap_or(0) + 1
    }

    pub fn find_by_email(&self, email: &str) -> Option<u32> {
        for (id_str, info) in &self.accounts {
            if info.email.eq_ignore_ascii_case(email) {
                return id_str.parse().ok();
            }
        }
        None
    }

    /// Resolve a CLI argument (account number or email) to a stored account number.
    pub fn find_by_id_or_email(&self, input: &str) -> Option<u32> {
        // Try as a number first.
        if let Ok(n) = input.parse::<u32>() {
            if self.accounts.contains_key(&n.to_string()) {
                return Some(n);
            }
        }
        // Fall back to email match.
        self.find_by_email(input)
    }
}

/// Path to ~/.cloaked/accounts.json
pub fn store_path() -> PathBuf {
    dirs::home_dir()
        .expect("cannot find home directory")
        .join(".cloaked")
        .join("accounts.json")
}

/// Path to ~/.cloaked/configs/
pub fn configs_dir() -> PathBuf {
    dirs::home_dir()
        .expect("cannot find home directory")
        .join(".cloaked")
        .join("configs")
}

/// Path to ~/.cloaked/credentials/
pub fn credentials_dir() -> PathBuf {
    dirs::home_dir()
        .expect("cannot find home directory")
        .join(".cloaked")
        .join("credentials")
}

pub fn load_store() -> Result<Store> {
    let path = store_path();
    if !path.exists() {
        return Ok(Store::new());
    }
    let data = fs::read_to_string(&path)
        .with_context(|| format!("failed to read {}", path.display()))?;
    let store: Store = serde_json::from_str(&data)
        .with_context(|| format!("failed to parse {}", path.display()))?;
    Ok(store)
}

pub fn save_store(store: &Store) -> Result<()> {
    let path = store_path();
    let dir = path.parent().expect("store path has no parent");

    fs::create_dir_all(dir)
        .with_context(|| format!("failed to create directory {}", dir.display()))?;
    fs::set_permissions(dir, fs::Permissions::from_mode(0o700))
        .with_context(|| format!("failed to set permissions on {}", dir.display()))?;

    let data = serde_json::to_string_pretty(store)?;
    let tmp = path.with_extension("json.tmp");
    fs::write(&tmp, &data)
        .with_context(|| format!("failed to write {}", tmp.display()))?;
    fs::rename(&tmp, &path)
        .with_context(|| format!("failed to rename {} -> {}", tmp.display(), path.display()))?;
    fs::set_permissions(&path, fs::Permissions::from_mode(0o600))
        .with_context(|| format!("failed to set permissions on {}", path.display()))?;

    Ok(())
}

/// Save the oauthAccount blob for one managed account.
pub fn save_account_config(account_num: u32, email: &str, oauth: &serde_json::Value) -> Result<()> {
    let dir = configs_dir();
    fs::create_dir_all(&dir)
        .with_context(|| format!("failed to create directory {}", dir.display()))?;

    let filename = format!("{}-{}.json", account_num, email);
    let path = dir.join(&filename);
    let data = serde_json::to_string_pretty(oauth)?;

    let tmp = path.with_extension("json.tmp");
    fs::write(&tmp, &data)
        .with_context(|| format!("failed to write {}", tmp.display()))?;
    fs::rename(&tmp, &path)
        .with_context(|| format!("failed to rename {} -> {}", tmp.display(), path.display()))?;
    fs::set_permissions(&path, fs::Permissions::from_mode(0o600))
        .with_context(|| format!("failed to set permissions on {}", path.display()))?;

    Ok(())
}

/// Load the oauthAccount blob for one managed account.
pub fn load_account_config(account_num: u32, email: &str) -> Result<serde_json::Value> {
    let filename = format!("{}-{}.json", account_num, email);
    let path = configs_dir().join(&filename);
    let data = fs::read_to_string(&path).with_context(|| {
        format!(
            "failed to read config for account {} ({}): {}",
            account_num,
            email,
            path.display()
        )
    })?;
    let value: serde_json::Value = serde_json::from_str(&data)?;
    Ok(value)
}

/// Delete the config file for one managed account. No-ops if already absent.
pub fn delete_account_config(account_num: u32, email: &str) -> Result<()> {
    let filename = format!("{}-{}.json", account_num, email);
    let path = configs_dir().join(&filename);
    if path.exists() {
        fs::remove_file(&path)
            .with_context(|| format!("failed to delete {}", path.display()))?;
    }
    Ok(())
}

// ---------------------------------------------------------------------------
// Credential file storage (OAuth tokens, stored as 600-permission files)
// Same security model as ~/.ssh/id_rsa or ~/.aws/credentials.
// Keeps all keychain interaction to only Claude Code's own entry.
// ---------------------------------------------------------------------------

fn credential_path(account_num: u32, email: &str) -> PathBuf {
    credentials_dir().join(format!("{}-{}.json", account_num, email))
}

pub fn save_account_credentials(account_num: u32, email: &str, credentials: &str) -> Result<()> {
    let dir = credentials_dir();
    fs::create_dir_all(&dir)
        .with_context(|| format!("failed to create {}", dir.display()))?;
    fs::set_permissions(&dir, fs::Permissions::from_mode(0o700))
        .with_context(|| format!("failed to set permissions on {}", dir.display()))?;

    let path = credential_path(account_num, email);
    let tmp = path.with_extension("json.tmp");
    fs::write(&tmp, credentials)
        .with_context(|| format!("failed to write {}", tmp.display()))?;
    fs::rename(&tmp, &path)
        .with_context(|| format!("failed to rename {} -> {}", tmp.display(), path.display()))?;
    fs::set_permissions(&path, fs::Permissions::from_mode(0o600))
        .with_context(|| format!("failed to set permissions on {}", path.display()))?;

    Ok(())
}

pub fn load_account_credentials(account_num: u32, email: &str) -> Result<String> {
    let path = credential_path(account_num, email);
    fs::read_to_string(&path).with_context(|| {
        format!(
            "failed to read credentials for account {} ({}) — try removing and re-adding",
            account_num, email
        )
    })
}

pub fn delete_account_credentials(account_num: u32, email: &str) -> Result<()> {
    let path = credential_path(account_num, email);
    if path.exists() {
        fs::remove_file(&path)
            .with_context(|| format!("failed to delete {}", path.display()))?;
    }
    Ok(())
}
