"""Authentication configuration storage with multi-org support and keychain token storage."""

import os
from pathlib import Path
from typing import TypedDict

import keyring
import yaml


# Keychain service name for token storage
KEYRING_SERVICE = "paracord-cli"


class EnvironmentConfig(TypedDict):
    api_url: str
    auth_url: str


class OrganizationInfo(TypedDict):
    """Organization metadata (tokens stored in keychain, not here)."""
    name: str


class CloudflareConfig(TypedDict, total=False):
    """Cloudflare tunnel configuration for a project."""
    subdomain: str


class ProjectInfo(TypedDict, total=False):
    """Project metadata for console registry."""
    name: str
    port: int  # Django port (backward compat, same as django_port)
    postgres_port: int
    redis_port: int
    template_source: str
    registered_at: str  # ISO format timestamp
    cloudflare: CloudflareConfig


class ParacordConfig(TypedDict, total=False):
    environments: dict[str, EnvironmentConfig]
    current_environment: str
    user_email: str
    organizations: dict[str, dict[str, OrganizationInfo]]  # env -> org_id -> info (no tokens!)
    path_organizations: dict[str, str]  # path -> org_id
    projects: dict[str, ProjectInfo]  # path -> project info


DEFAULT_CONFIG: ParacordConfig = {
    "environments": {
        "development": {
            "api_url": "https://api.desalsa.co",
            "auth_url": "https://dash.desalsa.co",
        },
        "production": {
            "api_url": "https://api.paracord.co",
            "auth_url": "https://dash.paracord.co",
        },
    },
    "current_environment": "production",
    "user_email": "",
    "organizations": {
        "development": {},
        "production": {},
    },
    "path_organizations": {},
    "projects": {},
}


# Old config format for migration
class OldCredentials(TypedDict, total=False):
    token: str
    organization_id: str
    organization_name: str
    user_email: str


class OldAuthConfig(TypedDict):
    environments: dict[str, EnvironmentConfig]
    current_environment: str
    credentials: dict[str, OldCredentials | None]


def get_config_path() -> Path:
    """Get the config path at ~/.paracord/config."""
    return Path.home() / ".paracord" / "config"


def get_old_config_path() -> Path:
    """Get the old config path for migration."""
    if os.name == "nt":  # Windows
        base = Path(os.environ.get("APPDATA", "~"))
    else:  # macOS/Linux
        base = Path(os.environ.get("XDG_CONFIG_HOME", "~/.config"))
    return base.expanduser() / "paracord" / "auth.yaml"


def _keyring_key(env: str, org_id: str) -> str:
    """Generate keyring key for an org's token."""
    return f"{env}:{org_id}"


def _get_token(env: str, org_id: str) -> str | None:
    """Get token from keychain."""
    try:
        return keyring.get_password(KEYRING_SERVICE, _keyring_key(env, org_id))
    except Exception:
        return None


def _set_token(env: str, org_id: str, token: str) -> None:
    """Store token in keychain."""
    keyring.set_password(KEYRING_SERVICE, _keyring_key(env, org_id), token)


def _delete_token(env: str, org_id: str) -> None:
    """Delete token from keychain."""
    try:
        keyring.delete_password(KEYRING_SERVICE, _keyring_key(env, org_id))
    except keyring.errors.PasswordDeleteError:
        pass  # Token didn't exist


def migrate_old_config() -> ParacordConfig | None:
    """Migrate from old config format if it exists.

    Returns migrated config or None if no migration needed.
    Tokens are migrated to the keychain.
    """
    old_path = get_old_config_path()
    if not old_path.exists():
        return None

    try:
        with open(old_path) as f:
            old_config: OldAuthConfig = yaml.safe_load(f)

        if not old_config:
            return None

        # Build new config from old
        new_config: ParacordConfig = {
            "environments": old_config.get("environments", DEFAULT_CONFIG["environments"]),
            "current_environment": old_config.get("current_environment", "production"),
            "user_email": "",
            "organizations": {"development": {}, "production": {}},
            "path_organizations": {},
        }

        # Migrate credentials (tokens go to keychain)
        old_creds = old_config.get("credentials", {})
        for env, creds in old_creds.items():
            if creds and creds.get("token") and creds.get("organization_id"):
                org_id = creds["organization_id"]
                # Store org info in config
                new_config["organizations"][env][org_id] = {
                    "name": creds.get("organization_name", ""),
                }
                # Store token in keychain
                _set_token(env, org_id, creds["token"])

                if creds.get("user_email"):
                    new_config["user_email"] = creds["user_email"]

        return new_config
    except Exception:
        return None


def get_config() -> ParacordConfig:
    """Load config from disk, migrating from old format if needed."""
    config_path = get_config_path()

    # If new config doesn't exist, try migration
    if not config_path.exists():
        migrated = migrate_old_config()
        if migrated:
            save_config(migrated)
            return migrated
        return DEFAULT_CONFIG.copy()

    with open(config_path) as f:
        config = yaml.safe_load(f)

    # Merge with defaults to ensure all keys exist
    merged: ParacordConfig = DEFAULT_CONFIG.copy()
    if config:
        merged.update(config)
        # Ensure nested dicts are merged properly
        if "environments" in config:
            merged["environments"] = {
                **DEFAULT_CONFIG["environments"],
                **config["environments"],
            }
        if "organizations" in config:
            merged["organizations"] = {
                "development": config.get("organizations", {}).get("development", {}),
                "production": config.get("organizations", {}).get("production", {}),
            }
        if "path_organizations" not in config:
            merged["path_organizations"] = {}
        if "projects" in config:
            merged["projects"] = config["projects"]
        elif "projects" not in config:
            merged["projects"] = {}
    return merged


def save_config(config: ParacordConfig) -> None:
    """Save config to disk (tokens are NOT stored here - they go to keychain)."""
    config_path = get_config_path()
    config_path.parent.mkdir(parents=True, exist_ok=True)

    with open(config_path, "w") as f:
        yaml.safe_dump(dict(config), f, default_flow_style=False)

    # Set restrictive permissions on Unix
    if os.name != "nt":
        config_path.chmod(0o600)


def get_current_environment() -> str:
    """Get the current environment name."""
    return get_config()["current_environment"]


def set_current_environment(env: str) -> None:
    """Set the current environment."""
    config = get_config()
    if env not in config["environments"]:
        raise ValueError(f"Unknown environment: {env}")
    config["current_environment"] = env
    save_config(config)


def get_environment_config(env: str | None = None) -> EnvironmentConfig:
    """Get configuration for an environment."""
    config = get_config()
    target_env = env or config["current_environment"]
    return config["environments"][target_env]


# --- Multi-org functions ---


class OrganizationCredentials(TypedDict):
    """Full org credentials including token (for API compatibility)."""
    name: str
    token: str


def add_organization(
    env: str,
    org_id: str,
    name: str,
    token: str,
    user_email: str | None = None,
) -> None:
    """Add or update an organization's credentials."""
    config = get_config()

    if env not in config["organizations"]:
        config["organizations"][env] = {}

    # Store org info in config (no token!)
    config["organizations"][env][org_id] = {
        "name": name,
    }

    # Store token in keychain
    _set_token(env, org_id, token)

    if user_email:
        config["user_email"] = user_email

    save_config(config)


def get_authenticated_orgs(env: str | None = None) -> dict[str, OrganizationCredentials]:
    """Get all authenticated organizations for an environment.

    Returns dict of org_id -> OrganizationCredentials (including tokens from keychain)
    Only returns orgs that have valid tokens in the keychain.
    """
    config = get_config()
    target_env = env or config["current_environment"]
    org_infos = config["organizations"].get(target_env, {})

    result: dict[str, OrganizationCredentials] = {}
    for org_id, info in org_infos.items():
        token = _get_token(target_env, org_id)
        if token:  # Only include if token exists in keychain
            result[org_id] = {
                "name": info.get("name", ""),
                "token": token,
            }

    return result


def get_org_credentials(env: str, org_id: str) -> OrganizationCredentials | None:
    """Get credentials for a specific organization."""
    config = get_config()
    org_info = config["organizations"].get(env, {}).get(org_id)
    if not org_info:
        return None

    token = _get_token(env, org_id)
    if not token:
        return None

    return {
        "name": org_info.get("name", ""),
        "token": token,
    }


def get_org_for_path(path: str | Path) -> str | None:
    """Get the organization ID mapped to a path.

    Walks up the directory tree to find the closest matching path.
    Returns org_id or None if no mapping found.
    """
    config = get_config()
    path_orgs = config.get("path_organizations", {})

    if not path_orgs:
        return None

    # Normalize the path
    check_path = Path(path).resolve()

    # Walk up the directory tree
    while True:
        path_str = str(check_path)
        if path_str in path_orgs:
            return path_orgs[path_str]

        parent = check_path.parent
        if parent == check_path:
            # Reached root
            break
        check_path = parent

    return None


def set_org_for_path(path: str | Path, org_id: str) -> None:
    """Set the organization for a directory path."""
    config = get_config()

    if "path_organizations" not in config:
        config["path_organizations"] = {}

    # Normalize and store
    path_str = str(Path(path).resolve())
    config["path_organizations"][path_str] = org_id
    save_config(config)


def clear_org_for_path(path: str | Path) -> bool:
    """Clear the organization mapping for a path.

    Returns True if a mapping was removed, False if none existed.
    """
    config = get_config()
    path_str = str(Path(path).resolve())

    if path_str in config.get("path_organizations", {}):
        del config["path_organizations"][path_str]
        save_config(config)
        return True
    return False


def clear_all_credentials() -> None:
    """Clear all credentials and path mappings (logout).

    Removes tokens from keychain and clears config.
    """
    config = get_config()

    # Delete all tokens from keychain
    for env, orgs in config.get("organizations", {}).items():
        for org_id in orgs.keys():
            _delete_token(env, org_id)

    # Clear config
    config["organizations"] = {"development": {}, "production": {}}
    config["path_organizations"] = {}
    config["user_email"] = ""
    save_config(config)


def get_user_email() -> str:
    """Get the stored user email."""
    return get_config().get("user_email", "")


# --- Legacy compatibility (for gradual migration) ---


class Credentials(TypedDict, total=False):
    """Legacy credentials format for compatibility."""
    token: str
    organization_id: str
    organization_name: str
    user_email: str


def get_current_credentials() -> Credentials | None:
    """Get credentials for the current directory/environment.

    This is a compatibility function that returns legacy-format credentials.
    For new code, use get_credentials_for_cwd() from core/api.py instead.
    """
    config = get_config()
    env = config["current_environment"]

    # First, check if there's a path-based org
    cwd = Path.cwd()
    org_id = get_org_for_path(cwd)

    if org_id:
        org_creds = get_org_credentials(env, org_id)
        if org_creds:
            return {
                "token": org_creds["token"],
                "organization_id": org_id,
                "organization_name": org_creds["name"],
                "user_email": config.get("user_email", ""),
            }

    # Fallback: if only one org is authenticated, use it
    orgs = get_authenticated_orgs(env)
    if len(orgs) == 1:
        org_id, org_creds = next(iter(orgs.items()))
        return {
            "token": org_creds["token"],
            "organization_id": org_id,
            "organization_name": org_creds["name"],
            "user_email": config.get("user_email", ""),
        }

    return None


def save_credentials(env: str, credentials: Credentials) -> None:
    """Save credentials (legacy compatibility).

    Converts to new multi-org format with keychain storage.
    """
    add_organization(
        env=env,
        org_id=credentials.get("organization_id", ""),
        name=credentials.get("organization_name", ""),
        token=credentials.get("token", ""),
        user_email=credentials.get("user_email"),
    )


def clear_credentials(env: str | None = None) -> None:
    """Clear credentials for an environment (legacy compatibility)."""
    config = get_config()
    target_env = env or config["current_environment"]

    # Delete tokens from keychain for this env
    for org_id in config["organizations"].get(target_env, {}).keys():
        _delete_token(target_env, org_id)

    config["organizations"][target_env] = {}
    save_config(config)
