# src/chuk_mcp_linkedin/api/posts.py
"""
LinkedIn Posts API operations.

Handles creating text posts, image posts, video posts, and other post types.
"""

from pathlib import Path
from typing import Any, Dict, List, Optional

import httpx

from .errors import LinkedInAPIError


class PostsAPIMixin:
    """
    Mixin providing LinkedIn Posts API operations.

    Requires the class to have:
    - self.access_token
    - self.person_urn
    - self._get_headers(use_rest_api=True)
    """

    async def create_text_post(
        self,
        text: str,
        visibility: str = "PUBLIC",
    ) -> Dict[str, Any]:
        """
        Create a text post on LinkedIn using the Posts API.

        Args:
            text: Post text/commentary
            visibility: Post visibility ("PUBLIC", "CONNECTIONS", or "LOGGED_IN")

        Returns:
            API response with post details

        Raises:
            LinkedInAPIError: If API call fails

        Reference:
            https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api
        """
        if not self.access_token or not self.person_urn:  # type: ignore[attr-defined]
            raise LinkedInAPIError(
                "LinkedIn API not configured. Access token and Person URN required (obtained via OAuth)"
            )

        # Build request payload using new Posts API format
        payload = {
            "author": self.person_urn,  # type: ignore[attr-defined]
            "commentary": text,
            "visibility": visibility,
            "lifecycleState": "PUBLISHED",
            "distribution": {"feedDistribution": "MAIN_FEED"},
        }

        # Use new Posts API endpoint
        url = "https://api.linkedin.com/rest/posts"

        async with httpx.AsyncClient() as client:
            try:
                response = await client.post(
                    url,
                    json=payload,
                    headers=self._get_headers(use_rest_api=True),  # type: ignore[attr-defined]
                    timeout=30.0,
                )

                # Check for errors
                if response.status_code not in (200, 201):
                    error_msg = f"LinkedIn API error: {response.status_code}"
                    try:
                        error_data = response.json()
                        error_msg += f" - {error_data}"
                    except Exception:
                        error_msg += f" - {response.text}"
                    raise LinkedInAPIError(error_msg)

                # Handle response - may be JSON or empty
                response_data = {
                    "status_code": response.status_code,
                    "headers": dict(response.headers),
                }

                # Try to parse JSON response if present
                if response.content:
                    try:
                        response_data.update(response.json())
                    except Exception:
                        response_data["text"] = response.text

                # Extract post ID from headers (LinkedIn returns it in x-restli-id)
                if "x-restli-id" in response.headers:
                    response_data["id"] = response.headers["x-restli-id"]

                return response_data

            except httpx.HTTPError as e:
                raise LinkedInAPIError(f"HTTP error while posting to LinkedIn: {str(e)}")

    async def create_image_post(
        self,
        text: str,
        image_path: str | Path,
        alt_text: Optional[str] = None,
        visibility: str = "PUBLIC",
    ) -> Dict[str, Any]:
        """
        Create a post with a single image on LinkedIn.

        Args:
            text: Post text/commentary
            image_path: Path to image file (JPG, PNG, GIF)
            alt_text: Optional alt text for accessibility
            visibility: Post visibility ("PUBLIC", "CONNECTIONS", or "LOGGED_IN")

        Returns:
            API response with post details

        Raises:
            LinkedInAPIError: If API call fails

        Reference:
            https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/images-api
        """
        # Step 1: Upload image (requires MediaAPIMixin)
        if not hasattr(self, "upload_image"):
            raise LinkedInAPIError(
                "upload_image method not available. Ensure MediaAPIMixin is included."
            )

        image_urn = await self.upload_image(image_path, alt_text)

        # Step 2: Create post with image
        payload = {
            "author": self.person_urn,  # type: ignore[attr-defined]
            "commentary": text,
            "visibility": visibility,
            "content": {"media": {"id": image_urn, "altText": alt_text or ""}},
            "lifecycleState": "PUBLISHED",
            "distribution": {"feedDistribution": "MAIN_FEED"},
        }

        return await self._create_post(payload)

    async def create_multi_image_post(
        self,
        text: str,
        image_paths: List[str | Path],
        alt_texts: Optional[List[str]] = None,
        visibility: str = "PUBLIC",
    ) -> Dict[str, Any]:
        """
        Create a carousel post with multiple images on LinkedIn.

        Args:
            text: Post text/commentary
            image_paths: List of paths to image files (2-20 images)
            alt_texts: Optional list of alt texts for each image
            visibility: Post visibility ("PUBLIC", "CONNECTIONS", or "LOGGED_IN")

        Returns:
            API response with post details

        Raises:
            LinkedInAPIError: If API call fails

        Reference:
            https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/multiimage-post-api

        Notes:
            - Minimum 2 images, maximum 20 images
            - Only for non-sponsored posts
        """
        # Validate image count
        if len(image_paths) < 2:
            raise LinkedInAPIError("Multi-image posts require at least 2 images")
        if len(image_paths) > 20:
            raise LinkedInAPIError("Multi-image posts support maximum 20 images")

        # Upload all images
        if not hasattr(self, "upload_image"):
            raise LinkedInAPIError(
                "upload_image method not available. Ensure MediaAPIMixin is included."
            )

        # Prepare alt texts
        if alt_texts is None:
            alt_texts = ["" for _ in image_paths]
        elif len(alt_texts) != len(image_paths):
            raise LinkedInAPIError("Number of alt texts must match number of images")

        # Upload images and build image array
        images = []
        for image_path, alt_text in zip(image_paths, alt_texts):
            image_urn = await self.upload_image(image_path, alt_text)
            images.append({"id": image_urn, "altText": alt_text})

        # Create post with multiple images
        payload = {
            "author": self.person_urn,  # type: ignore[attr-defined]
            "commentary": text,
            "visibility": visibility,
            "content": {"multiImage": {"images": images}},
            "lifecycleState": "PUBLISHED",
            "distribution": {"feedDistribution": "MAIN_FEED"},
        }

        return await self._create_post(payload)

    async def create_video_post(
        self,
        text: str,
        video_path: str | Path,
        title: Optional[str] = None,
        visibility: str = "PUBLIC",
    ) -> Dict[str, Any]:
        """
        Create a post with a video on LinkedIn.

        Args:
            text: Post text/commentary
            video_path: Path to video file (MP4 only)
            title: Optional video title
            visibility: Post visibility ("PUBLIC", "CONNECTIONS", or "LOGGED_IN")

        Returns:
            API response with post details

        Raises:
            LinkedInAPIError: If API call fails

        Reference:
            https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/videos-api

        Notes:
            - Format: MP4 only
            - Length: 3 seconds to 30 minutes
            - Size: 75kb - 500MB
        """
        # Step 1: Upload video (requires MediaAPIMixin)
        if not hasattr(self, "upload_video"):
            raise LinkedInAPIError(
                "upload_video method not available. Ensure MediaAPIMixin is included."
            )

        video_urn = await self.upload_video(video_path, title)

        # Step 2: Create post with video
        file_path = Path(video_path)
        video_title = title or file_path.stem

        payload = {
            "author": self.person_urn,  # type: ignore[attr-defined]
            "commentary": text,
            "visibility": visibility,
            "content": {"media": {"id": video_urn, "title": video_title}},
            "lifecycleState": "PUBLISHED",
            "distribution": {"feedDistribution": "MAIN_FEED"},
        }

        return await self._create_post(payload)

    async def create_poll_post(
        self,
        text: str,
        question: str,
        options: List[str],
        duration: str = "THREE_DAYS",
        visibility: str = "PUBLIC",
    ) -> Dict[str, Any]:
        """
        Create a poll post on LinkedIn.

        Args:
            text: Post text/commentary
            question: Poll question (max 140 characters)
            options: List of poll options (2-4 options, max 30 chars each)
            duration: Poll duration - "ONE_DAY", "THREE_DAYS", "ONE_WEEK", or "TWO_WEEKS"
            visibility: Post visibility ("PUBLIC", "CONNECTIONS", or "LOGGED_IN")

        Returns:
            API response with post details

        Raises:
            LinkedInAPIError: If API call fails

        Reference:
            https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/poll-post-api

        Example:
            >>> await client.create_poll_post(
            ...     "Quick question for my network:",
            ...     "What's your favorite programming language?",
            ...     ["Python", "JavaScript", "Go", "Rust"],
            ...     duration="ONE_WEEK"
            ... )

        Notes:
            - Minimum 2 options, maximum 4 options
            - Question max length: 140 characters
            - Option text max length: 30 characters
            - Only non-sponsored posts allowed
        """
        # Validate poll parameters
        if len(question) > 140:
            raise LinkedInAPIError(
                f"Poll question too long: {len(question)} chars. Maximum: 140 characters"
            )

        if len(options) < 2:
            raise LinkedInAPIError("Poll must have at least 2 options")
        if len(options) > 4:
            raise LinkedInAPIError("Poll can have maximum 4 options")

        for i, option in enumerate(options):
            if len(option) > 30:
                raise LinkedInAPIError(
                    f"Option {i + 1} too long: {len(option)} chars. Maximum: 30 characters"
                )

        # Validate duration
        valid_durations = ["ONE_DAY", "THREE_DAYS", "ONE_WEEK", "TWO_WEEKS"]
        if duration not in valid_durations:
            raise LinkedInAPIError(
                f"Invalid duration: {duration}. Valid: {', '.join(valid_durations)}"
            )

        # Build poll options
        poll_options = [{"text": option} for option in options]

        # Create poll post payload
        payload = {
            "author": self.person_urn,  # type: ignore[attr-defined]
            "commentary": text,
            "visibility": visibility,
            "content": {
                "poll": {
                    "question": question,
                    "options": poll_options,
                    "settings": {"duration": duration},
                }
            },
            "lifecycleState": "PUBLISHED",
            "distribution": {"feedDistribution": "MAIN_FEED"},
        }

        return await self._create_post(payload)

    async def _create_post(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """
        Internal helper to create a post with given payload.

        Args:
            payload: Post payload dict

        Returns:
            API response with post details

        Raises:
            LinkedInAPIError: If API call fails
        """
        url = "https://api.linkedin.com/rest/posts"

        async with httpx.AsyncClient() as client:
            try:
                response = await client.post(
                    url,
                    json=payload,
                    headers=self._get_headers(use_rest_api=True),  # type: ignore[attr-defined]
                    timeout=30.0,
                )

                if response.status_code not in (200, 201):
                    error_msg = f"LinkedIn API error: {response.status_code}"
                    try:
                        error_data = response.json()
                        error_msg += f" - {error_data}"
                    except Exception:
                        error_msg += f" - {response.text}"
                    raise LinkedInAPIError(error_msg)

                # Handle response - may be JSON or empty
                response_data = {
                    "status_code": response.status_code,
                    "headers": dict(response.headers),
                }

                # Try to parse JSON response if present
                if response.content:
                    try:
                        response_data.update(response.json())
                    except Exception:
                        response_data["text"] = response.text

                # Extract post ID from headers (LinkedIn returns it in x-restli-id)
                if "x-restli-id" in response.headers:
                    response_data["id"] = response.headers["x-restli-id"]

                return response_data

            except httpx.HTTPError as e:
                raise LinkedInAPIError(f"HTTP error while posting to LinkedIn: {str(e)}")
