"""
Post-migration cleanup: complete import mode and add members back to spaces.

Extracted from ``migrator.py`` to keep the orchestrator focused on control flow.
Each function receives only the specific dependencies it needs.
"""

from __future__ import annotations

import logging
import traceback
from pathlib import Path
from typing import TYPE_CHECKING

from google.auth.exceptions import RefreshError, TransportError
from googleapiclient.errors import HttpError

from slack_chat_migrator.constants import (
    HTTP_FORBIDDEN,
    HTTP_RATE_LIMIT,
    HTTP_SERVER_ERROR_MIN,
    SPACE_NAME_PREFIX,
    SPACES_PAGE_SIZE,
)
from slack_chat_migrator.services.spaces.regular_membership import add_regular_members
from slack_chat_migrator.utils.logging import log_with_context

if TYPE_CHECKING:
    from slack_chat_migrator.core.context import MigrationContext
    from slack_chat_migrator.core.state import MigrationState
    from slack_chat_migrator.services.chat_adapter import ChatAdapter
    from slack_chat_migrator.services.files.file import FileHandler
    from slack_chat_migrator.services.user_resolver import UserResolver


def cleanup_channel_handlers(state: MigrationState) -> None:
    """Clean up and close all channel-specific log handlers.

    Args:
        state: The migration state holding channel handlers.
    """
    if not state.spaces.channel_handlers:
        return

    logger = logging.getLogger("slack_chat_migrator")

    for channel_name, handler in list(state.spaces.channel_handlers.items()):
        try:
            handler.flush()
            handler.close()
            logger.removeHandler(handler)
            log_with_context(
                logging.DEBUG, f"Cleaned up log handler for channel: {channel_name}"
            )
        except OSError as e:
            # Use print to avoid potential logging issues during cleanup
            print(
                f"Warning: Failed to clean up log handler"
                f" for channel {channel_name}: {e}"
            )

    state.spaces.channel_handlers.clear()


def _list_spaces_in_import_mode(
    chat: ChatAdapter,
) -> list[tuple[str, dict]] | None:
    """List all spaces and filter to those still in import mode.

    Returns a list of (space_name, space_info) tuples, or None if the
    space listing itself failed (caller should abort cleanup).
    """
    log_with_context(logging.DEBUG, "Listing all spaces to check for import mode...")

    # Paginate through all spaces (the API returns at most SPACES_PAGE_SIZE
    # per call and a nextPageToken when more pages are available).
    all_spaces: list[dict] = []
    page_token: str | None = None
    try:
        while True:
            response = chat.list_spaces(
                page_size=SPACES_PAGE_SIZE, page_token=page_token
            )
            all_spaces.extend(response.get("spaces", []))
            page_token = response.get("nextPageToken")
            if not page_token:
                break
    except HttpError as http_e:
        log_with_context(
            logging.ERROR,
            f"HTTP error listing spaces during cleanup: {http_e}"
            f" (Status: {http_e.resp.status})",
            error_code=http_e.resp.status,
        )
        if http_e.resp.status >= HTTP_SERVER_ERROR_MIN:
            log_with_context(
                logging.WARNING,
                "Server error listing spaces"
                " - this might be a temporary issue, skipping cleanup",
            )
        return None
    except (RefreshError, TransportError) as list_e:
        log_with_context(
            logging.ERROR,
            f"Failed to list spaces during cleanup: {list_e}",
        )
        return None

    import_mode_spaces: list[tuple[str, dict]] = []
    for space in all_spaces:
        space_name = space.get("name", "")
        if not space_name:
            continue

        try:
            space_info = chat.get_space(space_name)
            if space_info.get("importMode"):
                import_mode_spaces.append((space_name, space_info))
        except HttpError as http_e:
            log_with_context(
                logging.WARNING,
                f"HTTP error checking space status during cleanup: {http_e}"
                f" (Status: {http_e.resp.status})",
                space_name=space_name,
                error_code=http_e.resp.status,
            )
            if http_e.resp.status >= HTTP_SERVER_ERROR_MIN:
                log_with_context(
                    logging.WARNING,
                    "Server error checking space - this might be a temporary issue",
                    space_name=space_name,
                )
        except (RefreshError, TransportError) as e:
            log_with_context(
                logging.WARNING,
                f"Failed to get space info during cleanup: {e}",
                space_name=space_name,
            )

    return import_mode_spaces


def run_cleanup(
    ctx: MigrationContext,
    state: MigrationState,
    chat: ChatAdapter,
    user_resolver: UserResolver,
    file_handler: FileHandler | None,
) -> None:
    """Complete import mode on spaces and add regular members back.

    This is the instance-level cleanup that runs after a migration. It
    inspects every space visible to the service account, finds those still
    in import mode, completes the import, and then attempts to add regular
    (non-historical) members.

    Args:
        ctx: Immutable migration context (config, paths, flags).
        state: Mutable migration state.
        chat: Google Chat API service.
        user_resolver: User identity resolver.
        file_handler: File handler for drive operations, or None.
    """
    state.context.current_channel = None

    if ctx.dry_run:
        log_with_context(logging.INFO, "[DRY RUN] Would perform post-migration cleanup")
        return

    log_with_context(logging.INFO, "Performing post-migration cleanup")

    try:
        import_mode_spaces = _list_spaces_in_import_mode(chat)
        if import_mode_spaces is None:
            return

        if import_mode_spaces:
            _complete_import_mode_spaces(
                ctx, state, chat, user_resolver, file_handler, import_mode_spaces
            )
        else:
            log_with_context(
                logging.INFO, "No spaces found in import mode during cleanup."
            )

    except HttpError as http_e:
        log_with_context(
            logging.ERROR,
            f"HTTP error during post-migration cleanup: {http_e}"
            f" (Status: {http_e.resp.status})",
            error_code=http_e.resp.status,
        )
        if http_e.resp.status >= HTTP_SERVER_ERROR_MIN:
            log_with_context(
                logging.WARNING,
                "Server error during cleanup"
                " - Google's servers may be experiencing issues",
            )
        elif http_e.resp.status == HTTP_FORBIDDEN:
            log_with_context(
                logging.WARNING,
                "Permission error during cleanup"
                " - service account may lack required permissions",
            )
        elif http_e.resp.status == HTTP_RATE_LIMIT:
            log_with_context(
                logging.WARNING,
                "Rate limit exceeded during cleanup - too many API requests",
            )
    except Exception as e:
        log_with_context(
            logging.ERROR,
            f"Unexpected error during cleanup: {e}",
        )
        log_with_context(
            logging.DEBUG,
            f"Cleanup exception traceback: {traceback.format_exc()}",
        )

    log_with_context(logging.INFO, "Cleanup completed")


def _complete_import_mode_spaces(
    ctx: MigrationContext,
    state: MigrationState,
    chat: ChatAdapter,
    user_resolver: UserResolver,
    file_handler: FileHandler | None,
    import_mode_spaces: list[tuple[str, dict]],
) -> None:
    """Complete import mode for discovered spaces and add members.

    Args:
        ctx: Immutable migration context.
        state: Mutable migration state.
        chat: Google Chat API service.
        user_resolver: User identity resolver.
        file_handler: File handler for drive operations, or None.
        import_mode_spaces: List of (space_name, space_info) tuples.
    """
    log_with_context(
        logging.INFO,
        f"Found {len(import_mode_spaces)} spaces still in import mode."
        " Attempting to complete import.",
    )

    log_with_context(
        logging.INFO,
        f"Current channel_to_space mapping: {state.spaces.channel_to_space}",
    )
    log_with_context(
        logging.INFO,
        f"Current created_spaces mapping: {state.spaces.created_spaces}",
    )

    for space_name, space_info in import_mode_spaces:
        log_with_context(
            logging.WARNING,
            f"Found space in import mode during cleanup: {space_name}",
        )

        try:
            _complete_single_space(
                ctx,
                state,
                chat,
                user_resolver,
                file_handler,
                space_name,
                space_info,
            )
        except HttpError as http_e:
            log_with_context(
                logging.ERROR,
                f"HTTP error during cleanup for space {space_name}: {http_e}"
                f" (Status: {http_e.resp.status})",
                space_name=space_name,
                error_code=http_e.resp.status,
            )
            if http_e.resp.status >= HTTP_SERVER_ERROR_MIN:
                log_with_context(
                    logging.WARNING,
                    "Server error during cleanup - this might be a temporary issue",
                    space_name=space_name,
                )
        except (RefreshError, TransportError) as e:
            log_with_context(
                logging.ERROR,
                f"Failed to complete import mode for space"
                f" {space_name} during cleanup: {e}",
                space_name=space_name,
            )


def _complete_single_space(
    ctx: MigrationContext,
    state: MigrationState,
    chat: ChatAdapter,
    user_resolver: UserResolver,
    file_handler: FileHandler | None,
    space_name: str,
    space_info: dict,
) -> None:
    """Complete import mode for a single space, preserve settings, and add members.

    Args:
        ctx: Immutable migration context.
        state: Mutable migration state.
        chat: Google Chat API service.
        user_resolver: User identity resolver.
        file_handler: File handler for drive operations, or None.
        space_name: The Google Chat space resource name (e.g. ``spaces/AAAA``).
        space_info: The space metadata dict from the API.
    """
    external_users_allowed = space_info.get("externalUserAllowed", False)

    if not external_users_allowed:
        external_users_allowed = state.progress.spaces_with_external_users.get(
            space_name, False
        )
        if external_users_allowed:
            log_with_context(
                logging.INFO,
                f"Space {space_name} has external users but flag not set,"
                " will enable after import",
                space_name=space_name,
            )

    log_with_context(
        logging.DEBUG,
        f"Attempting to complete import mode for space: {space_name}",
    )

    # --- Complete import mode ---------------------------------------------
    try:
        chat.complete_import(space_name)
        log_with_context(
            logging.DEBUG,
            f"Successfully completed import mode for space: {space_name}",
            space_name=space_name,
        )
    except HttpError as http_e:
        log_with_context(
            logging.ERROR,
            f"HTTP error completing import for space {space_name}: {http_e}"
            f" (Status: {http_e.resp.status})",
            space_name=space_name,
            error_code=http_e.resp.status,
        )
        if http_e.resp.status >= HTTP_SERVER_ERROR_MIN:
            log_with_context(
                logging.WARNING,
                "Server error completing import - this might be a temporary issue",
                space_name=space_name,
            )
        return
    except (RefreshError, TransportError) as e:
        log_with_context(
            logging.ERROR,
            f"Failed to complete import: {e}",
            space_name=space_name,
        )
        return

    # --- Preserve external user access ------------------------------------
    if external_users_allowed:
        try:
            chat.patch_space(
                name=space_name,
                update_mask="externalUserAllowed",
                body={"externalUserAllowed": True},
            )
            log_with_context(
                logging.INFO,
                f"Preserved external user access for space: {space_name}",
            )
        except HttpError as http_e:
            log_with_context(
                logging.WARNING,
                f"HTTP error preserving external user access for space"
                f" {space_name}: {http_e} (Status: {http_e.resp.status})",
                space_name=space_name,
                error_code=http_e.resp.status,
            )
            if http_e.resp.status >= HTTP_SERVER_ERROR_MIN:
                log_with_context(
                    logging.WARNING,
                    "Server error updating space - this might be a temporary issue",
                    space_name=space_name,
                )
        except (RefreshError, TransportError) as e:
            log_with_context(
                logging.WARNING,
                f"Failed to preserve external user access: {e}",
                space_name=space_name,
            )

    # --- Resolve channel name and add members -----------------------------
    channel_name = _resolve_channel_name(state, ctx.export_root, space_name, space_info)

    if channel_name:
        log_with_context(
            logging.INFO,
            f"Step 5/6: Adding regular members to space for channel: {channel_name}",
        )
        try:
            add_regular_members(
                ctx,
                state,
                chat,
                user_resolver,
                file_handler,
                space_name,
                channel_name,
            )
            log_with_context(
                logging.DEBUG,
                f"Successfully added regular members to space"
                f" {space_name} for channel: {channel_name}",
            )
        except Exception as e:
            log_with_context(
                logging.ERROR,
                f"Error adding regular members to space {space_name}: {e}",
                channel=channel_name,
            )
            log_with_context(
                logging.DEBUG,
                f"Exception traceback: {traceback.format_exc()}",
                channel=channel_name,
            )
    else:
        log_with_context(
            logging.WARNING,
            f"Could not determine channel name for space {space_name},"
            " skipping adding members",
            space_name=space_name,
        )


def _resolve_channel_name(
    state: MigrationState,
    export_root: Path,
    space_name: str,
    space_info: dict,
) -> str | None:
    """Try to find the Slack channel name that corresponds to a space.

    First checks the ``channel_to_space`` mapping, then falls back to
    matching the space display name against known channel names derived
    from the export directory.

    Args:
        state: Migration state with channel_to_space mapping.
        export_root: Path to the Slack export directory.
        space_name: The Google Chat space resource name.
        space_info: The space metadata dict from the API.

    Returns:
        The channel name if found, or ``None``.
    """
    # Try channel_to_space mapping first
    for ch, sp in state.spaces.channel_to_space.items():
        if sp == space_name:
            log_with_context(
                logging.INFO,
                f"Found channel {ch} for space {space_name}"
                " using channel_to_space mapping",
            )
            return ch

    # Fall back to display name matching
    display_name = space_info.get("displayName", "")
    log_with_context(
        logging.DEBUG,
        f"Attempting to extract channel name from display name: {display_name}",
    )

    all_channel_names = [d.name for d in export_root.iterdir() if d.is_dir()]
    for ch in all_channel_names:
        ch_display = f"{SPACE_NAME_PREFIX}{ch}"
        if ch_display in display_name:
            log_with_context(
                logging.INFO,
                f"Found channel {ch} for space {space_name} using display name",
            )
            return ch

    return None
