"""
Batch Operation Manager

This module provides high-level batch operation management for Google Docs,
extracting complex validation and request building logic.
"""

import logging
import asyncio
from typing import Any, Union, Dict, List, Tuple

from gdocs.docs_helpers import (
    create_insert_text_request,
    create_delete_range_request,
    create_format_text_request,
    create_update_paragraph_style_request,
    create_find_replace_request,
    create_insert_table_request,
    create_insert_page_break_request,
    create_bullet_list_request,
    create_delete_bullet_list_request,
    create_insert_doc_tab_request,
    create_delete_doc_tab_request,
    create_update_doc_tab_request,
    validate_operation,
)

logger = logging.getLogger(__name__)


class BatchOperationManager:
    """
    High-level manager for Google Docs batch operations.

    Handles complex multi-operation requests including:
    - Operation validation and request building
    - Batch execution with proper error handling
    - Operation result processing and reporting
    """

    def __init__(self, service):
        """
        Initialize the batch operation manager.

        Args:
            service: Google Docs API service instance
        """
        self.service = service

    async def execute_batch_operations(
        self, document_id: str, operations: list[dict[str, Any]]
    ) -> tuple[bool, str, dict[str, Any]]:
        """
        Execute multiple document operations in a single atomic batch.

        This method extracts the complex logic from batch_update_doc tool function.

        Args:
            document_id: ID of the document to update
            operations: List of operation dictionaries

        Returns:
            Tuple of (success, message, metadata)
        """
        logger.info(f"Executing batch operations on document {document_id}")
        logger.info(f"Operations count: {len(operations)}")

        if not operations:
            return (
                False,
                "No operations provided. Please provide at least one operation.",
                {},
            )

        try:
            # Validate and build requests
            requests, operation_descriptions = await self._validate_and_build_requests(
                operations
            )

            if not requests:
                return False, "No valid requests could be built from operations", {}

            # Execute the batch
            result = await self._execute_batch_requests(document_id, requests)

            # Process results
            metadata = {
                "operations_count": len(operations),
                "requests_count": len(requests),
                "replies_count": len(result.get("replies", [])),
                "operation_summary": operation_descriptions[:5],  # First 5 operations
            }

            # Extract new tab IDs from insert_doc_tab replies
            created_tabs = self._extract_created_tabs(result)
            if created_tabs:
                metadata["created_tabs"] = created_tabs

            summary = self._build_operation_summary(operation_descriptions)
            msg = f"Successfully executed {len(operations)} operations ({summary})"
            if created_tabs:
                tab_info = ", ".join(
                    f"'{t['title']}' (tab_id: {t['tab_id']})" for t in created_tabs
                )
                msg += f". Created tabs: {tab_info}"

            return True, msg, metadata

        except Exception as e:
            logger.error(f"Failed to execute batch operations: {str(e)}")
            return False, f"Batch operation failed: {str(e)}", {}

    async def _validate_and_build_requests(
        self, operations: list[dict[str, Any]]
    ) -> tuple[list[dict[str, Any]], list[str]]:
        """
        Validate operations and build API requests.

        Args:
            operations: List of operation dictionaries

        Returns:
            Tuple of (requests, operation_descriptions)
        """
        requests = []
        operation_descriptions = []

        for i, op in enumerate(operations):
            # Validate operation structure
            is_valid, error_msg = validate_operation(op)
            if not is_valid:
                raise ValueError(f"Operation {i + 1}: {error_msg}")

            op_type = op.get("type")

            try:
                # Build request based on operation type
                result = self._build_operation_request(op, op_type)

                # Handle both single request and list of requests
                if isinstance(result[0], list):
                    # Multiple requests (e.g., replace_text)
                    for req in result[0]:
                        requests.append(req)
                    operation_descriptions.append(result[1])
                elif result[0]:
                    # Single request
                    requests.append(result[0])
                    operation_descriptions.append(result[1])

            except KeyError as e:
                raise ValueError(
                    f"Operation {i + 1} ({op_type}) missing required field: {e}"
                )
            except Exception as e:
                raise ValueError(
                    f"Operation {i + 1} ({op_type}) failed validation: {str(e)}"
                )

        return requests, operation_descriptions

    def _build_operation_request(
        self, op: dict[str, Any], op_type: str
    ) -> Tuple[Union[Dict[str, Any], List[Dict[str, Any]]], str]:
        """
        Build a single operation request.

        Args:
            op: Operation dictionary
            op_type: Operation type

        Returns:
            Tuple of (request, description)
        """
        tab_id = op.get("tab_id")

        if op_type == "insert_text":
            request = create_insert_text_request(op["index"], op["text"], tab_id)
            description = f"insert text at {op['index']}"

        elif op_type == "delete_text":
            request = create_delete_range_request(
                op["start_index"], op["end_index"], tab_id
            )
            description = f"delete text {op['start_index']}-{op['end_index']}"

        elif op_type == "replace_text":
            # Replace is delete + insert (must be done in this order)
            delete_request = create_delete_range_request(
                op["start_index"], op["end_index"], tab_id
            )
            insert_request = create_insert_text_request(
                op["start_index"], op["text"], tab_id
            )
            # Return both requests as a list
            request = [delete_request, insert_request]
            description = f"replace text {op['start_index']}-{op['end_index']} with '{op['text'][:20]}{'...' if len(op['text']) > 20 else ''}'"

        elif op_type == "format_text":
            request = create_format_text_request(
                op["start_index"],
                op["end_index"],
                op.get("bold"),
                op.get("italic"),
                op.get("underline"),
                op.get("font_size"),
                op.get("font_family"),
                op.get("text_color"),
                op.get("background_color"),
                op.get("link_url"),
                tab_id,
            )

            if not request:
                raise ValueError("No formatting options provided")

            # Build format description
            format_changes = []
            for param, name in [
                ("bold", "bold"),
                ("italic", "italic"),
                ("underline", "underline"),
                ("font_size", "font size"),
                ("font_family", "font family"),
                ("text_color", "text color"),
                ("background_color", "background color"),
                ("link_url", "link"),
            ]:
                if op.get(param) is not None:
                    value = f"{op[param]}pt" if param == "font_size" else op[param]
                    format_changes.append(f"{name}: {value}")

            description = f"format text {op['start_index']}-{op['end_index']} ({', '.join(format_changes)})"

        elif op_type == "update_paragraph_style":
            request = create_update_paragraph_style_request(
                op["start_index"],
                op["end_index"],
                op.get("heading_level"),
                op.get("alignment"),
                op.get("line_spacing"),
                op.get("indent_first_line"),
                op.get("indent_start"),
                op.get("indent_end"),
                op.get("space_above"),
                op.get("space_below"),
                tab_id,
                op.get("named_style_type"),
            )

            if not request:
                raise ValueError("No paragraph style options provided")

            _PT_PARAMS = {
                "indent_first_line",
                "indent_start",
                "indent_end",
                "space_above",
                "space_below",
            }
            _SUFFIX = {
                "heading_level": lambda v: f"H{v}",
                "line_spacing": lambda v: f"{v}x",
            }

            style_changes = []
            for param, name in [
                ("heading_level", "heading"),
                ("alignment", "alignment"),
                ("line_spacing", "line spacing"),
                ("indent_first_line", "first line indent"),
                ("indent_start", "start indent"),
                ("indent_end", "end indent"),
                ("space_above", "space above"),
                ("space_below", "space below"),
            ]:
                if op.get(param) is not None:
                    raw = op[param]
                    fmt = _SUFFIX.get(param)
                    if fmt:
                        value = fmt(raw)
                    elif param in _PT_PARAMS:
                        value = f"{raw}pt"
                    else:
                        value = raw
                    style_changes.append(f"{name}: {value}")

            description = f"paragraph style {op['start_index']}-{op['end_index']} ({', '.join(style_changes)})"

        elif op_type == "insert_table":
            request = create_insert_table_request(
                op["index"], op["rows"], op["columns"], tab_id
            )
            description = f"insert {op['rows']}x{op['columns']} table at {op['index']}"

        elif op_type == "insert_page_break":
            request = create_insert_page_break_request(op["index"], tab_id)
            description = f"insert page break at {op['index']}"

        elif op_type == "find_replace":
            request = create_find_replace_request(
                op["find_text"], op["replace_text"], op.get("match_case", False), tab_id
            )
            description = f"find/replace '{op['find_text']}' → '{op['replace_text']}'"

        elif op_type == "create_bullet_list":
            list_type = op.get("list_type", "UNORDERED")
            if list_type not in ("UNORDERED", "ORDERED", "NONE"):
                raise ValueError(
                    f"Invalid list_type '{list_type}'. Must be 'UNORDERED', 'ORDERED', or 'NONE'"
                )
            if list_type == "NONE":
                request = create_delete_bullet_list_request(
                    op["start_index"], op["end_index"], tab_id
                )
                description = f"remove bullets {op['start_index']}-{op['end_index']}"
            else:
                request = create_bullet_list_request(
                    op["start_index"],
                    op["end_index"],
                    list_type,
                    op.get("nesting_level"),
                    op.get("paragraph_start_indices"),
                    tab_id,
                )
                style = "bulleted" if list_type == "UNORDERED" else "numbered"
                description = f"create {style} list {op['start_index']}-{op['end_index']}"
                if op.get("nesting_level"):
                    description += f" (nesting level {op['nesting_level']})"

        elif op_type == "insert_doc_tab":
            request = create_insert_doc_tab_request(
                op["title"], op["index"], op.get("parent_tab_id")
            )
            description = f"insert tab '{op['title']}' at {op['index']}"
            if op.get("parent_tab_id"):
                description += f" under parent tab {op['parent_tab_id']}"

        elif op_type == "delete_doc_tab":
            request = create_delete_doc_tab_request(op["tab_id"])
            description = f"delete tab '{op['tab_id']}'"

        elif op_type == "update_doc_tab":
            request = create_update_doc_tab_request(op["tab_id"], op["title"])
            description = f"rename tab '{op['tab_id']}' to '{op['title']}'"

        else:
            supported_types = [
                "insert_text",
                "delete_text",
                "replace_text",
                "format_text",
                "update_paragraph_style",
                "insert_table",
                "insert_page_break",
                "find_replace",
                "create_bullet_list",
                "insert_doc_tab",
                "delete_doc_tab",
                "update_doc_tab",
            ]
            raise ValueError(
                f"Unsupported operation type '{op_type}'. Supported: {', '.join(supported_types)}"
            )

        return request, description

    async def _execute_batch_requests(
        self, document_id: str, requests: list[dict[str, Any]]
    ) -> dict[str, Any]:
        """
        Execute the batch requests against the Google Docs API.

        Args:
            document_id: Document ID
            requests: List of API requests

        Returns:
            API response
        """
        return await asyncio.to_thread(
            self.service.documents()
            .batchUpdate(documentId=document_id, body={"requests": requests})
            .execute
        )

    def _extract_created_tabs(self, result: dict[str, Any]) -> list[dict[str, str]]:
        """
        Extract tab IDs from insert_doc_tab replies in the batchUpdate response.

        Args:
            result: The batchUpdate API response

        Returns:
            List of dicts with tab_id and title for each created tab
        """
        created_tabs = []
        for reply in result.get("replies", []):
            if "createDocumentTab" in reply:
                props = reply["createDocumentTab"].get("tabProperties", {})
                tab_id = props.get("tabId")
                title = props.get("title", "")
                if tab_id:
                    created_tabs.append({"tab_id": tab_id, "title": title})
        return created_tabs

    def _build_operation_summary(self, operation_descriptions: list[str]) -> str:
        """
        Build a concise summary of operations performed.

        Args:
            operation_descriptions: List of operation descriptions

        Returns:
            Summary string
        """
        if not operation_descriptions:
            return "no operations"

        summary_items = operation_descriptions[:3]  # Show first 3 operations
        summary = ", ".join(summary_items)

        if len(operation_descriptions) > 3:
            remaining = len(operation_descriptions) - 3
            summary += f" and {remaining} more operation{'s' if remaining > 1 else ''}"

        return summary

    def get_supported_operations(self) -> dict[str, Any]:
        """
        Get information about supported batch operations.

        Returns:
            Dictionary with supported operation types and their required parameters
        """
        return {
            "supported_operations": {
                "insert_text": {
                    "required": ["index", "text"],
                    "description": "Insert text at specified index",
                },
                "delete_text": {
                    "required": ["start_index", "end_index"],
                    "description": "Delete text in specified range",
                },
                "replace_text": {
                    "required": ["start_index", "end_index", "text"],
                    "description": "Replace text in range with new text",
                },
                "format_text": {
                    "required": ["start_index", "end_index"],
                    "optional": [
                        "bold",
                        "italic",
                        "underline",
                        "font_size",
                        "font_family",
                        "text_color",
                        "background_color",
                        "link_url",
                    ],
                    "description": "Apply formatting to text range",
                },
                "update_paragraph_style": {
                    "required": ["start_index", "end_index"],
                    "optional": [
                        "heading_level",
                        "alignment",
                        "line_spacing",
                        "indent_first_line",
                        "indent_start",
                        "indent_end",
                        "space_above",
                        "space_below",
                    ],
                    "description": "Apply paragraph-level styling (headings, alignment, spacing, indentation)",
                },
                "insert_table": {
                    "required": ["index", "rows", "columns"],
                    "description": "Insert table at specified index",
                },
                "insert_page_break": {
                    "required": ["index"],
                    "description": "Insert page break at specified index",
                },
                "find_replace": {
                    "required": ["find_text", "replace_text"],
                    "optional": ["match_case"],
                    "description": "Find and replace text throughout document",
                },
                "create_bullet_list": {
                    "required": ["start_index", "end_index"],
                    "optional": ["list_type", "nesting_level", "paragraph_start_indices"],
                    "description": "Apply or remove native bullet/numbered list formatting (list_type: UNORDERED, ORDERED, or NONE to remove; nesting_level: 0-8)",
                },
                "insert_doc_tab": {
                    "required": ["title", "index"],
                    "description": "Insert a new document tab with given title at specified index",
                },
                "delete_doc_tab": {
                    "required": ["tab_id"],
                    "description": "Delete a document tab by its ID",
                },
                "update_doc_tab": {
                    "required": ["tab_id", "title"],
                    "description": "Rename a document tab",
                },
            },
            "example_operations": [
                {"type": "insert_text", "index": 1, "text": "Hello World"},
                {
                    "type": "format_text",
                    "start_index": 1,
                    "end_index": 12,
                    "bold": True,
                },
                {"type": "insert_table", "index": 20, "rows": 2, "columns": 3},
                {
                    "type": "update_paragraph_style",
                    "start_index": 1,
                    "end_index": 20,
                    "heading_level": 1,
                    "alignment": "CENTER",
                },
            ],
        }
