"""
LinkedIn API service for auto-posting.

Handles:
- UGC Posts API (v2) for posting content
- OAuth token refresh
- Image upload for posts with media
"""

import asyncio
import logging
from datetime import datetime, timedelta, timezone
from typing import Dict, Optional
from uuid import UUID

import httpx

from src.config import settings
from src.database.client import db
from src.utils.encryption import decrypt_token, encrypt_token

logger = logging.getLogger(__name__)


class LinkedInService:
    """Service for LinkedIn API interactions."""

    BASE_URL = "https://api.linkedin.com/v2"
    TIMEOUT = 30.0

    def __init__(self):
        self.db = db

    async def post_to_linkedin(
        self,
        linkedin_account_id: UUID,
        text: str,
        image_url: Optional[str] = None,
        media_items: Optional[list] = None
    ) -> Dict:
        """
        Post content to LinkedIn using UGC Posts API.

        Args:
            linkedin_account_id: ID of the linkedin_accounts record
            text: Post text content
            image_url: (DEPRECATED) Optional single image URL - use media_items instead
            media_items: Optional list of media items (dicts with 'type', 'url', 'order')
                        Supports up to 3 images (carousel) or 1 video

        Returns:
            Dict with 'url' key containing the LinkedIn post URL

        Raises:
            Exception: On API errors
        """
        # Get account with decrypted token
        account = await self._get_account_with_token(linkedin_account_id)
        if not account:
            raise ValueError(f"LinkedIn account {linkedin_account_id} not found")

        # Check token expiry
        if account['token_expires_at'] < datetime.now(timezone.utc):
            raise ValueError("Access token expired - refresh needed")

        access_token = account['access_token']
        linkedin_user_id = account['linkedin_user_id']

        # Build post payload
        payload = {
            "author": f"urn:li:person:{linkedin_user_id}",
            "lifecycleState": "PUBLISHED",
            "specificContent": {
                "com.linkedin.ugc.ShareContent": {
                    "shareCommentary": {
                        "text": text
                    },
                    "shareMediaCategory": "NONE"
                }
            },
            "visibility": {
                "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"
            }
        }

        # Handle media upload (new multi-media support)
        if media_items and len(media_items) > 0:
            try:
                media_type = media_items[0].get("type", "image")  # All items have same type

                if media_type == "video":
                    # Single video upload
                    video_urn = await self._upload_video(
                        access_token,
                        linkedin_user_id,
                        media_items[0].get("url")
                    )
                    if video_urn:
                        payload["specificContent"]["com.linkedin.ugc.ShareContent"]["shareMediaCategory"] = "VIDEO"
                        payload["specificContent"]["com.linkedin.ugc.ShareContent"]["media"] = [
                            {"status": "READY", "media": video_urn}
                        ]

                elif media_type == "image":
                    # Upload all images (Carousel if > 1)
                    media_urns = []
                    for item in sorted(media_items, key=lambda x: x.get("order", 0)):
                        urn = await self._upload_image(
                            access_token,
                            linkedin_user_id,
                            item.get("url")
                        )
                        if urn:
                            media_urns.append(urn)

                    if media_urns:
                        payload["specificContent"]["com.linkedin.ugc.ShareContent"]["shareMediaCategory"] = "IMAGE"
                        payload["specificContent"]["com.linkedin.ugc.ShareContent"]["media"] = [
                            {"status": "READY", "media": urn} for urn in media_urns
                        ]

            except Exception as e:
                logger.warning(f"Media upload failed for {linkedin_account_id}: {e}. Posting without media.")

        # Backward compatibility: single image_url
        elif image_url:
            try:
                media_urn = await self._upload_image(access_token, linkedin_user_id, image_url)
                if media_urn:
                    payload["specificContent"]["com.linkedin.ugc.ShareContent"]["shareMediaCategory"] = "IMAGE"
                    payload["specificContent"]["com.linkedin.ugc.ShareContent"]["media"] = [
                        {"status": "READY", "media": media_urn}
                    ]
            except Exception as e:
                logger.warning(f"Image upload failed for {linkedin_account_id}: {e}. Posting without image.")

        # Post to LinkedIn
        async with httpx.AsyncClient(timeout=self.TIMEOUT) as client:
            response = await client.post(
                f"{self.BASE_URL}/ugcPosts",
                headers={
                    "Authorization": f"Bearer {access_token}",
                    "Content-Type": "application/json",
                    "X-Restli-Protocol-Version": "2.0.0"
                },
                json=payload
            )

            if response.status_code == 201:
                # Success
                post_id = response.json().get("id")
                await self._update_account_last_used(linkedin_account_id)

                # LinkedIn post URL format (best effort)
                post_url = f"https://www.linkedin.com/feed/update/{post_id}/" if post_id else None

                logger.info(f"Posted to LinkedIn: {post_url}")
                return {"url": post_url, "post_id": post_id}

            elif response.status_code == 401:
                # Token expired or invalid
                await self._mark_account_error(
                    linkedin_account_id,
                    f"Token expired or invalid (401)"
                )
                raise Exception("LinkedIn authentication failed - token may be expired")

            elif response.status_code == 429:
                # Rate limit
                await self._mark_account_error(
                    linkedin_account_id,
                    "Rate limit exceeded (429)"
                )
                raise Exception("LinkedIn rate limit exceeded")

            else:
                # Other error
                error_msg = f"LinkedIn API error {response.status_code}: {response.text}"
                await self._mark_account_error(linkedin_account_id, error_msg)
                raise Exception(error_msg)

    async def _upload_image(
        self,
        access_token: str,
        linkedin_user_id: str,
        image_url: str
    ) -> Optional[str]:
        """
        Upload image to LinkedIn for use in post.

        Returns media URN if successful, None otherwise.
        """
        try:
            # Step 1: Register upload
            register_payload = {
                "registerUploadRequest": {
                    "recipes": ["urn:li:digitalmediaRecipe:feedshare-image"],
                    "owner": f"urn:li:person:{linkedin_user_id}",
                    "serviceRelationships": [
                        {
                            "relationshipType": "OWNER",
                            "identifier": "urn:li:userGeneratedContent"
                        }
                    ]
                }
            }

            async with httpx.AsyncClient(timeout=self.TIMEOUT) as client:
                register_response = await client.post(
                    f"{self.BASE_URL}/assets?action=registerUpload",
                    headers={
                        "Authorization": f"Bearer {access_token}",
                        "Content-Type": "application/json"
                    },
                    json=register_payload
                )

                if register_response.status_code != 200:
                    logger.error(f"Image register failed: {register_response.status_code}")
                    return None

                register_data = register_response.json()
                upload_url = register_data["value"]["uploadMechanism"]["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"]["uploadUrl"]
                asset_urn = register_data["value"]["asset"]

                # Step 2: Download image from Supabase
                image_response = await client.get(image_url)
                if image_response.status_code != 200:
                    logger.error(f"Failed to download image from {image_url}")
                    return None

                image_data = image_response.content

                # Step 3: Upload to LinkedIn
                upload_response = await client.put(
                    upload_url,
                    headers={
                        "Authorization": f"Bearer {access_token}",
                        "Content-Type": "application/octet-stream"
                    },
                    content=image_data
                )

                if upload_response.status_code in [200, 201]:
                    logger.info(f"Image uploaded successfully: {asset_urn}")
                    return asset_urn
                else:
                    logger.error(f"Image upload failed: {upload_response.status_code}")
                    return None

        except Exception as e:
            logger.error(f"Image upload error: {e}")
            return None

    async def _upload_video(
        self,
        access_token: str,
        linkedin_user_id: str,
        video_url: str
    ) -> Optional[str]:
        """
        Upload video to LinkedIn for use in post.

        Returns media URN if successful, None otherwise.
        """
        try:
            # Step 1: Register upload with VIDEO recipe
            register_payload = {
                "registerUploadRequest": {
                    "recipes": ["urn:li:digitalmediaRecipe:feedshare-video"],  # VIDEO recipe
                    "owner": f"urn:li:person:{linkedin_user_id}",
                    "serviceRelationships": [
                        {
                            "relationshipType": "OWNER",
                            "identifier": "urn:li:userGeneratedContent"
                        }
                    ]
                }
            }

            async with httpx.AsyncClient(timeout=60.0) as client:  # Longer timeout for videos
                register_response = await client.post(
                    f"{self.BASE_URL}/assets?action=registerUpload",
                    headers={
                        "Authorization": f"Bearer {access_token}",
                        "Content-Type": "application/json"
                    },
                    json=register_payload
                )

                if register_response.status_code != 200:
                    logger.error(f"Video register failed: {register_response.status_code}")
                    return None

                register_data = register_response.json()
                upload_url = register_data["value"]["uploadMechanism"]["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"]["uploadUrl"]
                asset_urn = register_data["value"]["asset"]

                # Step 2: Download video from Supabase
                video_response = await client.get(video_url)
                if video_response.status_code != 200:
                    logger.error(f"Failed to download video from {video_url}")
                    return None

                video_data = video_response.content

                # Step 3: Upload to LinkedIn
                upload_response = await client.put(
                    upload_url,
                    headers={
                        "Authorization": f"Bearer {access_token}",
                        "Content-Type": "application/octet-stream"
                    },
                    content=video_data
                )

                if upload_response.status_code in [200, 201]:
                    logger.info(f"Video uploaded successfully: {asset_urn}")
                    return asset_urn
                else:
                    logger.error(f"Video upload failed: {upload_response.status_code}")
                    return None

        except Exception as e:
            logger.error(f"Video upload error: {e}")
            return None

    async def refresh_access_token(self, linkedin_account_id: UUID) -> bool:
        """
        Attempt to refresh the access token using refresh token.

        Note: LinkedIn's OAuth 2.0 may not support refresh tokens for all scopes.
        Check LinkedIn documentation for current support.

        Returns:
            True if refresh succeeded, False otherwise
        """
        account = await self._get_account_with_token(linkedin_account_id)
        if not account or not account.get('refresh_token'):
            logger.warning(f"No refresh token for account {linkedin_account_id}")
            return False

        refresh_token = account['refresh_token']

        try:
            async with httpx.AsyncClient(timeout=self.TIMEOUT) as client:
                response = await client.post(
                    "https://www.linkedin.com/oauth/v2/accessToken",
                    data={
                        "grant_type": "refresh_token",
                        "refresh_token": refresh_token,
                        "client_id": settings.linkedin_client_id,
                        "client_secret": settings.linkedin_client_secret
                    },
                    headers={"Content-Type": "application/x-www-form-urlencoded"}
                )

                if response.status_code == 200:
                    token_data = response.json()
                    new_access_token = token_data["access_token"]
                    expires_in = token_data.get("expires_in", 5184000)  # Default 60 days

                    # Encrypt and update tokens
                    encrypted_access = encrypt_token(new_access_token)
                    new_refresh = token_data.get("refresh_token")
                    encrypted_refresh = encrypt_token(new_refresh) if new_refresh else account['refresh_token']

                    await self.db.update_linkedin_account(
                        linkedin_account_id,
                        {
                            "access_token": encrypted_access,
                            "refresh_token": encrypted_refresh,
                            "token_expires_at": datetime.now(timezone.utc) + timedelta(seconds=expires_in),
                            "last_error": None,
                            "last_error_at": None
                        }
                    )

                    logger.info(f"Token refreshed for account {linkedin_account_id}")
                    return True
                else:
                    logger.error(f"Token refresh failed: {response.status_code} - {response.text}")
                    return False

        except Exception as e:
            logger.error(f"Token refresh error: {e}")
            return False

    async def _get_account_with_token(self, linkedin_account_id: UUID) -> Optional[Dict]:
        """Get account and decrypt tokens."""
        account = await self.db.get_linkedin_account_by_id(linkedin_account_id)
        if not account:
            return None

        # Decrypt tokens
        account_dict = account.model_dump()
        account_dict['access_token'] = decrypt_token(account.access_token)
        if account.refresh_token:
            account_dict['refresh_token'] = decrypt_token(account.refresh_token)

        return account_dict

    async def _update_account_last_used(self, linkedin_account_id: UUID):
        """Update last_used_at timestamp."""
        await self.db.update_linkedin_account(
            linkedin_account_id,
            {
                "last_used_at": datetime.now(timezone.utc),
                "last_error": None,
                "last_error_at": None
            }
        )

    async def _mark_account_error(self, linkedin_account_id: UUID, error_msg: str):
        """Mark account with error."""
        await self.db.update_linkedin_account(
            linkedin_account_id,
            {
                "last_error": error_msg,
                "last_error_at": datetime.now(timezone.utc)
            }
        )


# Global instance
linkedin_service = LinkedInService()
