"""
Complete Batch API Implementation for Dynamics 365
Handles multipart/mixed batch requests and responses
"""

import uuid
import json
import logging
import time
from typing import List, Dict, Any, Optional

logger = logging.getLogger(__name__)


async def execute_batch(
    integration, requests: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
    """
    Execute batch API request with complete multipart implementation

    Args:
        integration: DynamicsIntegration instance
        requests: List of request dictionaries with method, url, headers, body

    Returns:
        List of response dictionaries with status, headers, body, error
    """
    start_time = time.time()

    if not requests:
        return []

    # Validate batch size (Dynamics limit is 100)
    if len(requests) > 100:
        raise ValueError(f"Batch size {len(requests)} exceeds maximum of 100")

    # Generate batch and changeset boundaries
    batch_boundary = f"batch_{uuid.uuid4()}"
    changeset_boundary = f"changeset_{uuid.uuid4()}"

    # Build multipart request body
    batch_body = _build_batch_body(requests, batch_boundary, changeset_boundary)

    # Prepare headers
    headers = integration._get_headers()
    headers["Content-Type"] = f"multipart/mixed;boundary={batch_boundary}"
    headers["Accept"] = "application/json"

    # Make batch request
    batch_url = f"{integration.credentials.resource}/api/data/v9.2/$batch"

    try:
        async with await integration._make_request(
            "POST", batch_url, headers=headers, data=batch_body
        ) as resp:
            if resp.status == 200:
                # Parse multipart response
                content_type = resp.headers.get("Content-Type", "")
                response_body = await resp.text()

                results = _parse_batch_response(response_body, content_type)

                # Audit batch operation
                if integration.audit:
                    successful = sum(1 for r in results if r.get("status", 0) < 400)
                    failed = len(results) - successful

                    await integration.audit.log_event(
                        "dynamics_batch_operation",
                        {
                            "total_requests": len(requests),
                            "successful": successful,
                            "failed": failed,
                            "duration_ms": (time.time() - start_time) * 1000,
                        },
                    )

                return results
            else:
                # Batch-level error
                error_text = await resp.text()

                if integration.audit:
                    await integration.audit.log_error(
                        "batch_request_failed", error_text
                    )

                return [
                    {
                        "status": resp.status,
                        "error": f"Batch request failed: {error_text}",
                        "headers": dict(resp.headers),
                        "body": None,
                    }
                ] * len(requests)

    except Exception as e:
        if integration.audit:
            await integration.audit.log_error("batch_execution_error", str(e))

        # Return error for all requests in batch
        return [
            {
                "status": 500,
                "error": f"Batch execution error: {str(e)}",
                "headers": {},
                "body": None,
            }
        ] * len(requests)


def _build_batch_body(
    requests: List[Dict[str, Any]], batch_boundary: str, changeset_boundary: str
) -> str:
    """Build multipart/mixed batch request body"""
    lines = []

    # Check if we need a changeset (for non-GET requests)
    has_mutations = any(req["method"] != "GET" for req in requests)

    if has_mutations:
        # Start batch with changeset
        lines.append(f"--{batch_boundary}")
        lines.append(f"Content-Type: multipart/mixed;boundary={changeset_boundary}")
        lines.append("")

        # Add each request to changeset
        for idx, request in enumerate(requests):
            lines.append(f"--{changeset_boundary}")
            lines.extend(_build_request_part(request, idx + 1))

        lines.append(f"--{changeset_boundary}--")
    else:
        # GET requests don't need changeset
        for idx, request in enumerate(requests):
            lines.append(f"--{batch_boundary}")
            lines.extend(_build_request_part(request, idx + 1))

    lines.append(f"--{batch_boundary}--")

    return "\r\n".join(lines)


def _build_request_part(request: Dict[str, Any], content_id: int) -> List[str]:
    """Build individual request part for batch"""
    lines = []

    # Content headers
    lines.append("Content-Type: application/http")
    lines.append("Content-Transfer-Encoding: binary")
    lines.append(f"Content-ID: {content_id}")
    lines.append("")

    # Request line
    url = request["url"]
    if not url.startswith("/"):
        url = f"/{url}"
    lines.append(f"{request['method']} {url} HTTP/1.1")

    # Headers
    if request.get("headers"):
        for header, value in request["headers"].items():
            lines.append(f"{header}: {value}")

    # Add required headers if not present
    if "Content-Type" not in request.get("headers", {}):
        lines.append("Content-Type: application/json")
    lines.append("Accept: application/json")

    # Body (if present)
    if request.get("body") is not None:
        body_json = json.dumps(request["body"], separators=(",", ":"))
        lines.append(f"Content-Length: {len(body_json)}")
        lines.append("")
        lines.append(body_json)
    else:
        lines.append("")

    lines.append("")  # Extra blank line after each request

    return lines


def _parse_batch_response(
    response_body: str, content_type: str
) -> List[Dict[str, Any]]:
    """Parse multipart batch response"""
    responses = []

    # Extract boundary from content-type
    boundary = None
    if "boundary=" in content_type:
        boundary = content_type.split("boundary=")[1].split(";")[0].strip('"')

    if not boundary:
        return [
            {
                "status": 500,
                "error": "Failed to parse batch response: no boundary found",
                "headers": {},
                "body": None,
            }
        ]

    # Split response by boundary
    parts = response_body.split(f"--{boundary}")

    for part in parts[1:-1]:  # Skip first (empty) and last (closing)
        if not part.strip():
            continue

        # Check if this is a changeset
        if "Content-Type: multipart/mixed" in part:
            # Parse nested changeset
            changeset_boundary = None
            for line in part.split("\r\n"):
                if "boundary=" in line:
                    changeset_boundary = line.split("boundary=")[1].strip('"')
                    break

            if changeset_boundary:
                # Parse changeset parts
                changeset_parts = part.split(f"--{changeset_boundary}")
                for cs_part in changeset_parts[1:-1]:
                    if cs_part.strip():
                        response = _parse_response_part(cs_part)
                        if response:
                            responses.append(response)
        else:
            # Single response (GET requests)
            response = _parse_response_part(part)
            if response:
                responses.append(response)

    return responses


def _parse_response_part(part: str) -> Optional[Dict[str, Any]]:
    """Parse individual response part from batch"""
    try:
        # Split headers and body
        sections = part.split("\r\n\r\n", 2)
        if len(sections) < 2:
            return None

        headers_section = sections[1] if len(sections) > 2 else sections[0]
        body_section = sections[2] if len(sections) > 2 else sections[1]

        # Parse status line
        lines = headers_section.strip().split("\r\n")
        if not lines:
            return None

        status_line = lines[0]
        if "HTTP" in status_line:
            parts = status_line.split(" ", 2)
            status_code = int(parts[1]) if len(parts) > 1 else 500
        else:
            return None

        # Parse headers
        headers = {}
        for line in lines[1:]:
            if ": " in line:
                key, value = line.split(": ", 1)
                headers[key] = value

        # Parse body
        body = None
        error = None

        if body_section.strip():
            try:
                # Try to parse as JSON
                body = json.loads(body_section.strip())

                # Check for OData error
                if "error" in body:
                    error = body["error"].get("message", "Unknown error")
            except json.JSONDecodeError:
                # Body is not JSON
                body = body_section.strip()

        # Build response
        response = {"status": status_code, "headers": headers, "body": body}

        if error:
            response["error"] = error
        elif status_code >= 400:
            response["error"] = f"Request failed with status {status_code}"

        return response

    except Exception as e:
        return {
            "status": 500,
            "error": f"Failed to parse response part: {str(e)}",
            "headers": {},
            "body": None,
        }


class BatchRequestBuilder:
    """
    Helper class to build batch requests
    Exported for use by other modules
    """

    def __init__(self):
        self.requests = []

    def add_create(
        self, entity_set: str, data: Dict[str, Any]
    ) -> "BatchRequestBuilder":
        """Add a create request to the batch"""
        self.requests.append(
            {
                "method": "POST",
                "url": f"{entity_set}",
                "headers": {"Content-Type": "application/json"},
                "body": data,
            }
        )
        return self

    def add_update(
        self, entity_set: str, entity_id: str, data: Dict[str, Any]
    ) -> "BatchRequestBuilder":
        """Add an update request to the batch"""
        self.requests.append(
            {
                "method": "PATCH",
                "url": f"{entity_set}({entity_id})",
                "headers": {"Content-Type": "application/json"},
                "body": data,
            }
        )
        return self

    def add_delete(self, entity_set: str, entity_id: str) -> "BatchRequestBuilder":
        """Add a delete request to the batch"""
        self.requests.append(
            {
                "method": "DELETE",
                "url": f"{entity_set}({entity_id})",
                "headers": {},
                "body": None,
            }
        )
        return self

    def add_retrieve(
        self,
        entity_set: str,
        entity_id: str,
        select: Optional[List[str]] = None,
        expand: Optional[List[str]] = None,
    ) -> "BatchRequestBuilder":
        """Add a retrieve request to the batch"""
        url = f"{entity_set}({entity_id})"
        params = []

        if select:
            params.append(f"$select={','.join(select)}")
        if expand:
            params.append(f"$expand={','.join(expand)}")

        if params:
            url += f"?{'&'.join(params)}"

        self.requests.append(
            {
                "method": "GET",
                "url": url,
                "headers": {"Accept": "application/json"},
                "body": None,
            }
        )
        return self

    def add_custom(
        self,
        method: str,
        url: str,
        headers: Optional[Dict[str, str]] = None,
        body: Optional[Any] = None,
    ) -> "BatchRequestBuilder":
        """Add a custom request to the batch"""
        self.requests.append(
            {"method": method, "url": url, "headers": headers or {}, "body": body}
        )
        return self

    def get_requests(self) -> List[Dict[str, Any]]:
        """Get the built requests"""
        return self.requests

    def clear(self) -> "BatchRequestBuilder":
        """Clear all requests"""
        self.requests = []
        return self
