use std::path::Path;

use anyhow::{Context, Result};
use serde::Deserialize;

use super::identifiers::{ApiToken, ProjectSlug, WorkspaceSlug};
use super::plane_settings::{DefaultTarget, WorkspaceProjectScope, load_plane_settings};

/// Resolved runtime state for executing Plane API commands.
/// Created by resolving the persisted settings file and PLANE_API_KEY env var
/// into concrete, validated credentials and workspace targeting.
pub struct PlaneSession {
    api_token: ApiToken,
    workspace_slug: WorkspaceSlug,
    project_scope: ProjectScope,
}

/// Whether commands default to a specific project or require an explicit --project flag.
/// Checked by commands that need a project context to decide whether to use the default
/// or require the user to pass --project.
pub enum ProjectScope {
    /// No default project configured — commands that need a project require --project
    ExplicitOnly,
    /// A default project is configured — commands use this unless overridden with --project
    Scoped(ProjectSlug),
}

/// Whether the project scope was determined by a local .plane.toml override
/// or should be read from the workspace's own config entry.
enum ProjectScopeOverride {
    /// .plane.toml provided an explicit project scope — use it directly
    FromLocalConfig(ProjectScope),
    /// No local override — read the project from the workspace entry in global config
    FromWorkspaceEntry,
}

/// Where the API token was sourced from during session resolution.
/// Named outcomes for the env-var-vs-config lookup to satisfy exhaustive handling.
enum ApiTokenSource {
    /// Token came from the PLANE_API_KEY environment variable, overriding config
    FromEnvironment(ApiToken),
    /// Env var not set — token should be read from the workspace config entry
    FromWorkspaceConfig,
}

/// Accessors and destructuring for the resolved session fields
impl PlaneSession {
    /// Returns the API token for authenticating Plane API requests
    pub fn api_token(&self) -> &ApiToken {
        &self.api_token
    }

    /// Returns the workspace slug for constructing API URL paths
    pub fn workspace_slug(&self) -> &WorkspaceSlug {
        &self.workspace_slug
    }

    /// Returns the project scope indicating whether a default project is set
    pub fn project_scope(&self) -> &ProjectScope {
        &self.project_scope
    }

    /// Consumes the session and returns owned parts for transferring to the API client,
    /// avoiding clones by giving up the session's credential and targeting data
    pub fn into_parts(self) -> (ApiToken, WorkspaceSlug, ProjectScope) {
        (self.api_token, self.workspace_slug, self.project_scope)
    }
}

fn resolve_api_token_source() -> Result<ApiTokenSource> {
    match std::env::var("PLANE_API_KEY") {
        Ok(env_token_text) => {
            let validated_token: ApiToken = env_token_text.parse()?;
            Ok(ApiTokenSource::FromEnvironment(validated_token))
        }
        Err(std::env::VarError::NotPresent) => Ok(ApiTokenSource::FromWorkspaceConfig),
        Err(std::env::VarError::NotUnicode(invalid_os_value)) => {
            anyhow::bail!(
                "PLANE_API_KEY contains invalid unicode: {:?}",
                invalid_os_value
            )
        }
    }
}

/// Per-directory project config parsed from `.plane.toml`.
/// Overrides the global active workspace and default project
/// so concurrent agents in different repos don't fight over config.
#[derive(Deserialize)]
struct LocalPlaneConfig {
    workspace: String,
    project: Option<String>,
}

/// Whether a local `.plane.toml` was found in the directory ancestry.
enum LocalConfigSearch {
    Found(LocalPlaneConfig),
    NotFound,
}

/// Walks from `start_directory` up to the filesystem root looking for `.plane.toml`.
/// Returns `Found` with the parsed config from the first match, or `NotFound`
/// if no `.plane.toml` exists in any ancestor directory.
fn find_local_plane_config(start_directory: &Path) -> LocalConfigSearch {
    let mut search_directory = start_directory;
    loop {
        let candidate_path = search_directory.join(".plane.toml");
        if candidate_path.is_file() {
            let toml_content = match std::fs::read_to_string(&candidate_path) {
                Ok(content) => content,
                Err(_read_error) => return LocalConfigSearch::NotFound,
            };
            return match toml::from_str(&toml_content) {
                Ok(parsed_config) => LocalConfigSearch::Found(parsed_config),
                Err(_parse_error) => LocalConfigSearch::NotFound,
            };
        }
        match search_directory.parent() {
            Some(parent_directory) => search_directory = parent_directory,
            None => return LocalConfigSearch::NotFound,
        }
    }
}

/// Resolves workspace and project targeting from a local `.plane.toml` config.
fn resolve_local_targeting(local_config: LocalPlaneConfig) -> Result<(WorkspaceSlug, ProjectScope)> {
    let workspace_slug: WorkspaceSlug = local_config.workspace.parse()
        .context("invalid workspace slug in .plane.toml")?;
    let scope = match local_config.project {
        Some(project_text) => {
            let project_slug: ProjectSlug = project_text.parse()
                .context("invalid project slug in .plane.toml")?;
            ProjectScope::Scoped(project_slug)
        }
        None => ProjectScope::ExplicitOnly,
    };
    Ok((workspace_slug, scope))
}

/// Resolves workspace targeting from the global config's default target.
/// Project scope is resolved separately from the workspace entry.
fn resolve_global_workspace(default_target: DefaultTarget) -> Result<WorkspaceSlug> {
    match default_target {
        DefaultTarget::Unconfigured => {
            anyhow::bail!("no workspace configured — run `plane config` first or add a .plane.toml");
        }
        DefaultTarget::WorkspaceOnly(workspace_slug) => Ok(workspace_slug),
    }
}

/// Loads settings from disk, resolves the active workspace and API token,
/// and constructs a ready-to-use session.
///
/// Resolution priority (highest wins):
/// 1. `.plane.toml` in current directory or any ancestor (per-project override)
/// 2. `~/.plane-cli-requiem/config.toml` global config (active workspace + default project)
/// 3. `PLANE_API_KEY` env var overrides the token from either source
pub fn resolve_plane_session() -> Result<PlaneSession> {
    let plane_settings = load_plane_settings()?;
    let (default_target, mut workspace_registry) = plane_settings.into_parts();

    let working_directory = std::env::current_dir()
        .context("could not determine current directory")?;
    let local_search = find_local_plane_config(&working_directory);

    let (active_slug, project_scope_override) = match local_search {
        LocalConfigSearch::Found(local_config) => {
            let (slug, scope) = resolve_local_targeting(local_config)?;
            (slug, ProjectScopeOverride::FromLocalConfig(scope))
        }
        LocalConfigSearch::NotFound => {
            let slug = resolve_global_workspace(default_target)?;
            (slug, ProjectScopeOverride::FromWorkspaceEntry)
        }
    };

    let workspace_entry = workspace_registry
        .remove_workspace(&active_slug)
        .with_context(|| format!("workspace '{}' not found in config — run `plane config --workspace {} --token <key>`", active_slug, active_slug))?;

    let (workspace_token, workspace_project_scope) = workspace_entry.into_parts();

    let project_scope = match project_scope_override {
        ProjectScopeOverride::FromLocalConfig(scope) => scope,
        ProjectScopeOverride::FromWorkspaceEntry => {
            match workspace_project_scope {
                WorkspaceProjectScope::Scoped(project_slug) => ProjectScope::Scoped(project_slug),
                WorkspaceProjectScope::Unscoped => ProjectScope::ExplicitOnly,
            }
        }
    };

    let resolved_token = match resolve_api_token_source()? {
        ApiTokenSource::FromEnvironment(env_token) => env_token,
        ApiTokenSource::FromWorkspaceConfig => workspace_token,
    };

    Ok(PlaneSession {
        api_token: resolved_token,
        workspace_slug: active_slug,
        project_scope,
    })
}
