"""
Blog Webhook API for Django Lotus Extras.

Secure webhook endpoint for creating blog articles via external services
like n8n, Zapier, or custom integrations.

Authentication: Bearer token in Authorization header
Configure: LOTUS_EXTRAS['WEBHOOK_SECRET'] in settings

Usage:
    POST /api/blog/webhook/
    Headers:
        Authorization: Bearer <WEBHOOK_SECRET>
        Content-Type: application/json
    Body:
        {
            "title": "Article Title",
            "slug": "article-slug",  # optional, auto-generated if not provided
            "introduction": "<p>Brief intro...</p>",
            "content": "<h2>Section</h2><p>Content...</p>",
            "seo_title": "SEO Title (max 60 chars)",  # optional
            "lead": "Meta description (max 160 chars)",  # optional
            "category_slug": "news",  # optional, defaults to 'news'
            "author_id": 2,  # optional, defaults to first admin user
            "publish_date": "2025-12-13",  # optional, defaults to today
            "publish_time": "10:00:00",  # optional, defaults to 10:00
            "status": "published",  # optional: "published" or "draft"
            "cta_id": 1,  # optional, ID of a BlogCallToAction to append
            "cover_image_url": "https://example.com/cover.jpg",  # optional
            "cover_image_alt_text": "Cover image description",  # optional
            "featured_image_url": "https://example.com/featured.jpg",  # optional
            "featured_image_alt_text": "Featured image description"  # optional
        }
"""
import json
import logging
import os
import re
import requests
from datetime import date, time, datetime
from urllib.parse import urlparse
from io import BytesIO

from django.core.files import File
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.utils.text import slugify

from .conf import django_blog_plus_settings
from .models import BlogCallToAction, ArticleCallToAction

logger = logging.getLogger(__name__)


def verify_webhook_token(request):
    """Verify the Authorization header contains a valid Bearer token."""
    auth_header = request.headers.get('Authorization', '')

    if not auth_header.startswith('Bearer '):
        return False

    token = auth_header[7:]  # Remove 'Bearer ' prefix
    expected_token = django_blog_plus_settings.get_webhook_secret()

    if not expected_token:
        logger.warning("LOTUS_EXTRAS['WEBHOOK_SECRET'] is not configured!")
        return False

    return token == expected_token


def generate_unique_slug(title, publish_date, language='en-gb'):
    """Generate a unique slug for the article."""
    from lotus.models import Article
    
    base_slug = slugify(title)[:50]  # Limit slug length
    slug = base_slug
    counter = 1

    while Article.objects.filter(
        slug=slug,
        publish_date=publish_date,
        language=language
    ).exists():
        slug = f"{base_slug}-{counter}"
        counter += 1

    return slug


def parse_blog_url(url):
    """
    Parse a blog URL and extract (year, month, day, slug).

    Accepts full URLs or path-only:
        - https://example.com/blog/2026/1/22/my-article-slug/
        - /blog/2026/1/22/my-article-slug/

    Returns:
        tuple: (year, month, day, slug) as (int, int, int, str), or None if invalid
    """
    if not url:
        return None

    # Extract path from full URL if needed
    if url.startswith('http://') or url.startswith('https://'):
        parsed = urlparse(url)
        path = parsed.path
    else:
        path = url

    # Match pattern: /blog/{year}/{month}/{day}/{slug}/
    pattern = r'/blog/(\d{4})/(\d{1,2})/(\d{1,2})/([^/]+)/?$'
    match = re.search(pattern, path)

    if not match:
        return None

    try:
        year = int(match.group(1))
        month = int(match.group(2))
        day = int(match.group(3))
        slug = match.group(4)

        # Basic validation
        if not (1 <= month <= 12 and 1 <= day <= 31):
            return None

        return (year, month, day, slug)
    except (ValueError, IndexError):
        return None


def get_article_by_url(url, language='en-gb'):
    """
    Look up an Article by its blog URL.

    Args:
        url: Full blog URL or path (e.g., /blog/2026/1/22/my-article/)
        language: Language code to filter by

    Returns:
        Article instance or None if not found
    """
    from lotus.models import Article
    
    parsed = parse_blog_url(url)
    if not parsed:
        return None

    year, month, day, slug = parsed

    return Article.objects.filter(
        publish_date__year=year,
        publish_date__month=month,
        publish_date__day=day,
        slug=slug,
        language=language
    ).first()


def download_and_save_image(image_url):
    """
    Download an image from a URL and prepare it for Django FileField.

    Args:
        image_url: URL of the image to download

    Returns:
        File object ready to be assigned to a FileField, or None if download failed
    """
    if not image_url:
        return None

    try:
        # Validate URL
        parsed = urlparse(image_url)
        if not parsed.scheme or not parsed.netloc:
            logger.warning(f"Invalid image URL: {image_url}")
            return None

        # Download image
        response = requests.get(image_url, timeout=30, stream=True)
        response.raise_for_status()

        # Check content type
        content_type = response.headers.get('content-type', '').lower()
        if not content_type.startswith('image/'):
            logger.warning(f"URL does not point to an image: {image_url} (content-type: {content_type})")
            return None

        # Get file extension from URL or content type
        ext = os.path.splitext(parsed.path)[1].lower()
        if not ext or ext not in ['.jpg', '.jpeg', '.png', '.gif', '.webp']:
            # Try to get extension from content type
            if 'jpeg' in content_type or 'jpg' in content_type:
                ext = '.jpg'
            elif 'png' in content_type:
                ext = '.png'
            elif 'gif' in content_type:
                ext = '.gif'
            elif 'webp' in content_type:
                ext = '.webp'
            else:
                ext = '.jpg'  # Default

        # Read image data
        image_data = BytesIO(response.content)

        # Check file size (max 10MB)
        if len(response.content) > 10 * 1024 * 1024:
            logger.warning(f"Image too large: {len(response.content)} bytes (max 10MB)")
            return None

        # Create a Django File object
        filename = f"webhook-{os.urandom(8).hex()}{ext}"
        django_file = File(image_data, name=filename)

        return django_file

    except requests.RequestException as e:
        logger.error(f"Failed to download image from {image_url}: {e}")
        return None
    except Exception as e:
        logger.error(f"Unexpected error downloading image from {image_url}: {e}")
        return None


@csrf_exempt
@require_http_methods(["POST"])
def blog_webhook(request):
    """
    Create a new blog article via webhook.

    Requires Bearer token authentication.
    """
    from lotus.models import Article, Category, Author
    
    # Verify authentication
    if not verify_webhook_token(request):
        logger.warning(f"Unauthorized blog webhook attempt from {request.META.get('REMOTE_ADDR')}")
        return JsonResponse({
            'success': False,
            'error': 'Unauthorized. Please provide a valid Bearer token.'
        }, status=401)

    try:
        # Parse JSON body
        try:
            data = json.loads(request.body)
        except json.JSONDecodeError:
            return JsonResponse({
                'success': False,
                'error': 'Invalid JSON in request body'
            }, status=400)

        # Get language from settings
        language = django_blog_plus_settings.get_language_code()

        # Validate required fields
        if not data.get('title'):
            return JsonResponse({
                'success': False,
                'error': 'Title is required'
            }, status=400)

        if not data.get('content'):
            return JsonResponse({
                'success': False,
                'error': 'Content is required'
            }, status=400)

        # Parse publish date
        publish_date_str = data.get('publish_date')
        if publish_date_str:
            try:
                publish_date_val = datetime.strptime(publish_date_str, '%Y-%m-%d').date()
            except ValueError:
                return JsonResponse({
                    'success': False,
                    'error': 'Invalid publish_date format. Use YYYY-MM-DD.'
                }, status=400)
        else:
            publish_date_val = date.today()

        # Parse publish time
        publish_time_str = data.get('publish_time')
        if publish_time_str:
            try:
                publish_time_val = datetime.strptime(publish_time_str, '%H:%M:%S').time()
            except ValueError:
                try:
                    publish_time_val = datetime.strptime(publish_time_str, '%H:%M').time()
                except ValueError:
                    return JsonResponse({
                        'success': False,
                        'error': 'Invalid publish_time format. Use HH:MM:SS or HH:MM.'
                    }, status=400)
        else:
            publish_time_val = time(10, 0, 0)

        # Generate or validate slug
        slug = data.get('slug')
        if not slug:
            slug = generate_unique_slug(data['title'], publish_date_val, language)
        else:
            slug = slugify(slug)
            # Check for duplicate
            if Article.objects.filter(
                slug=slug,
                publish_date=publish_date_val,
                language=language
            ).exists():
                return JsonResponse({
                    'success': False,
                    'error': f'An article with slug "{slug}" already exists for this date.'
                }, status=400)

        # Determine status
        status_str = data.get('status', 'published').lower()
        article_status = 10 if status_str == 'published' else 0  # 10=Published, 0=Draft

        # Get author
        author_id = data.get('author_id')
        if author_id:
            try:
                author = Author.objects.get(id=author_id)
            except Author.DoesNotExist:
                return JsonResponse({
                    'success': False,
                    'error': f'Author with ID {author_id} not found.'
                }, status=400)
        else:
            # Default to first staff user
            author = Author.objects.filter(is_staff=True).first()
            if not author:
                author = Author.objects.first()

        # Get category
        category_slug = data.get('category_slug', 'news')
        try:
            category = Category.objects.get(slug=category_slug, language=language)
        except Category.DoesNotExist:
            # Try to get any category
            category = Category.objects.filter(language=language).first()
            if not category:
                return JsonResponse({
                    'success': False,
                    'error': f'Category "{category_slug}" not found and no default category exists.'
                }, status=400)

        # Create the article
        article = Article.objects.create(
            language=language,
            status=article_status,
            title=data['title'],
            slug=slug,
            seo_title=data.get('seo_title', '')[:70] if data.get('seo_title') else '',
            lead=data.get('lead', '')[:200] if data.get('lead') else '',
            introduction=data.get('introduction', ''),
            content=data['content'],
            publish_date=publish_date_val,
            publish_time=publish_time_val,
        )

        # Handle cover image
        cover_image_url = data.get('cover_image_url')
        if cover_image_url:
            try:
                cover_file = download_and_save_image(cover_image_url)
                if cover_file:
                    article.cover = cover_file
                    if data.get('cover_image_alt_text'):
                        article.cover_alt_text = data.get('cover_image_alt_text')[:200]
                    logger.info(f"Downloaded and saved cover image for article #{article.id}")
            except Exception as e:
                logger.error(f"Error processing cover image: {e}")

        # Handle featured image (shown before CTA)
        featured_image_url = data.get('featured_image_url')
        if featured_image_url:
            try:
                featured_file = download_and_save_image(featured_image_url)
                if featured_file:
                    article.image = featured_file
                    if data.get('featured_image_alt_text'):
                        article.image_alt_text = data.get('featured_image_alt_text')[:200]
                    logger.info(f"Downloaded and saved featured image for article #{article.id}")
            except Exception as e:
                logger.error(f"Error processing featured image: {e}")

        # Save article again if images were added
        if cover_image_url or featured_image_url:
            article.save()

        # Add author and category
        if author:
            article.authors.add(author)
        article.categories.add(category)
        article.save()

        # Handle CTA assignment
        cta_id = data.get('cta_id')
        cta_assigned = None
        if cta_id:
            try:
                cta = BlogCallToAction.objects.get(id=cta_id)
                if not cta.is_active:
                    logger.warning(f"CTA {cta_id} is inactive, skipping assignment")
                else:
                    ArticleCallToAction.objects.create(
                        article_id=article.id,
                        cta=cta
                    )
                    cta_assigned = {
                        'id': cta.id,
                        'title': cta.title
                    }
                    logger.info(f"Assigned CTA {cta.id} to article {article.id}")
            except BlogCallToAction.DoesNotExist:
                logger.warning(f"CTA with ID {cta_id} not found, skipping assignment")

        logger.info(f"Blog article created via webhook: {article.id} - {article.title}")

        # Build response
        response_data = {
            'success': True,
            'article': {
                'id': article.id,
                'title': article.title,
                'slug': article.slug,
                'status': 'published' if article.status == 10 else 'draft',
                'publish_date': str(article.publish_date),
                'url': article.get_absolute_url(),
                'admin_url': f'/admin/lotus/article/{article.id}/change/',
                'cta': cta_assigned,
            }
        }

        return JsonResponse(response_data, status=201)

    except Exception as e:
        logger.exception(f"Error in blog webhook: {e}")
        return JsonResponse({
            'success': False,
            'error': f'Internal server error: {str(e)}'
        }, status=500)


@csrf_exempt
@require_http_methods(["PATCH"])
def blog_webhook_update(request):
    """
    Update an existing blog article via webhook.

    Requires Bearer token authentication.
    The article is identified by its URL in the request body.
    """
    # Verify authentication
    if not verify_webhook_token(request):
        logger.warning(f"Unauthorized blog webhook update attempt from {request.META.get('REMOTE_ADDR')}")
        return JsonResponse({
            'success': False,
            'error': 'Unauthorized. Please provide a valid Bearer token.'
        }, status=401)

    try:
        # Parse JSON body
        try:
            data = json.loads(request.body)
        except json.JSONDecodeError:
            return JsonResponse({
                'success': False,
                'error': 'Invalid JSON in request body'
            }, status=400)

        language = django_blog_plus_settings.get_language_code()

        # Validate required field: url
        article_url = data.get('url')
        if not article_url:
            return JsonResponse({
                'success': False,
                'error': 'The "url" field is required to identify the article to update.'
            }, status=400)

        # Look up the article
        article = get_article_by_url(article_url, language)
        if not article:
            return JsonResponse({
                'success': False,
                'error': f'Article not found for URL: {article_url}'
            }, status=404)

        # Track what was updated
        updated_fields = []

        # Update text fields (only if provided)
        if 'title' in data:
            article.title = data['title']
            updated_fields.append('title')

        if 'seo_title' in data:
            article.seo_title = data['seo_title'][:70] if data['seo_title'] else ''
            updated_fields.append('seo_title')

        if 'lead' in data:
            article.lead = data['lead'][:200] if data['lead'] else ''
            updated_fields.append('lead')

        if 'introduction' in data:
            article.introduction = data['introduction']
            updated_fields.append('introduction')

        if 'content' in data:
            article.content = data['content']
            updated_fields.append('content')

        # Update status
        if 'status' in data:
            status_str = data['status'].lower()
            article.status = 10 if status_str == 'published' else 0
            updated_fields.append('status')

        # Handle cover image update
        if 'cover_image_url' in data:
            cover_image_url = data['cover_image_url']
            if cover_image_url:
                try:
                    cover_file = download_and_save_image(cover_image_url)
                    if cover_file:
                        article.cover = cover_file
                        updated_fields.append('cover')
                except Exception as e:
                    logger.error(f"Error processing cover image: {e}")

        if 'cover_image_alt_text' in data:
            article.cover_alt_text = data['cover_image_alt_text'][:200] if data['cover_image_alt_text'] else ''
            updated_fields.append('cover_alt_text')

        # Handle featured image update
        if 'featured_image_url' in data:
            featured_image_url = data['featured_image_url']
            if featured_image_url:
                try:
                    featured_file = download_and_save_image(featured_image_url)
                    if featured_file:
                        article.image = featured_file
                        updated_fields.append('image')
                except Exception as e:
                    logger.error(f"Error processing featured image: {e}")

        if 'featured_image_alt_text' in data:
            article.image_alt_text = data['featured_image_alt_text'][:200] if data['featured_image_alt_text'] else ''
            updated_fields.append('image_alt_text')

        # Handle CTA update
        cta_updated = None
        if 'cta_id' in data:
            cta_id = data['cta_id']
            # Remove existing CTA assignment
            ArticleCallToAction.objects.filter(article_id=article.id).delete()

            if cta_id:
                try:
                    cta = BlogCallToAction.objects.get(id=cta_id)
                    if cta.is_active:
                        ArticleCallToAction.objects.create(
                            article_id=article.id,
                            cta=cta
                        )
                        cta_updated = {'id': cta.id, 'title': cta.title}
                        updated_fields.append('cta')
                except BlogCallToAction.DoesNotExist:
                    logger.warning(f"CTA with ID {cta_id} not found")
            else:
                updated_fields.append('cta')

        # Save the article
        if updated_fields:
            article.save()
            logger.info(f"Blog article updated via webhook: {article.id} - {article.title}")

        # Build response
        response_data = {
            'success': True,
            'article': {
                'id': article.id,
                'title': article.title,
                'slug': article.slug,
                'status': 'published' if article.status == 10 else 'draft',
                'publish_date': str(article.publish_date),
                'url': article.get_absolute_url(),
                'admin_url': f'/admin/lotus/article/{article.id}/change/',
                'cta': cta_updated,
            },
            'updated_fields': updated_fields
        }

        return JsonResponse(response_data, status=200)

    except Exception as e:
        logger.exception(f"Error in blog webhook update: {e}")
        return JsonResponse({
            'success': False,
            'error': f'Internal server error: {str(e)}'
        }, status=500)


@csrf_exempt
@require_http_methods(["GET"])
def blog_webhook_get(request):
    """
    Get article details by URL.

    Requires Bearer token authentication.
    """
    # Verify authentication
    if not verify_webhook_token(request):
        return JsonResponse({
            'success': False,
            'error': 'Unauthorized. Please provide a valid Bearer token.'
        }, status=401)

    language = django_blog_plus_settings.get_language_code()

    # Get URL from query parameter
    article_url = request.GET.get('url')
    if not article_url:
        return JsonResponse({
            'success': False,
            'error': 'The "url" query parameter is required.'
        }, status=400)

    # Look up the article
    article = get_article_by_url(article_url, language)
    if not article:
        return JsonResponse({
            'success': False,
            'error': f'Article not found for URL: {article_url}'
        }, status=404)

    # Get CTA info if assigned
    cta_info = None
    try:
        article_cta = ArticleCallToAction.objects.filter(article_id=article.id).first()
        if article_cta and article_cta.cta:
            cta_info = {
                'id': article_cta.cta.id,
                'title': article_cta.cta.title
            }
    except Exception:
        pass

    # Build response
    response_data = {
        'success': True,
        'article': {
            'id': article.id,
            'title': article.title,
            'slug': article.slug,
            'seo_title': article.seo_title or '',
            'lead': article.lead or '',
            'introduction': article.introduction or '',
            'content': article.content or '',
            'status': 'published' if article.status == 10 else 'draft',
            'publish_date': str(article.publish_date),
            'publish_time': str(article.publish_time) if article.publish_time else None,
            'url': article.get_absolute_url(),
            'admin_url': f'/admin/lotus/article/{article.id}/change/',
            'cta': cta_info,
        }
    }

    return JsonResponse(response_data, status=200)


@csrf_exempt
@require_http_methods(["GET"])
def blog_webhook_info(request):
    """
    Return information about the blog webhook API.
    No authentication required for this endpoint.
    """
    # Get available CTAs for documentation
    available_ctas = list(BlogCallToAction.objects.filter(is_active=True).values('id', 'title'))

    return JsonResponse({
        'name': 'Lotus Extras Blog Webhook',
        'version': '1.0',
        'endpoints': {
            'get_article': {
                'method': 'GET',
                'url': '/api/blog/webhook/article/',
                'authentication': 'Bearer token in Authorization header',
                'query_parameters': {
                    'url': 'Full blog URL (e.g., https://example.com/blog/2026/1/22/slug/)'
                }
            },
            'create_article': {
                'method': 'POST',
                'url': '/api/blog/webhook/',
                'authentication': 'Bearer token in Authorization header',
                'content_type': 'application/json',
                'required_fields': ['title', 'content'],
                'optional_fields': [
                    'slug', 'introduction', 'seo_title', 'lead',
                    'category_slug', 'author_id', 'publish_date',
                    'publish_time', 'status', 'cta_id',
                    'cover_image_url', 'cover_image_alt_text',
                    'featured_image_url', 'featured_image_alt_text'
                ]
            },
            'update_article': {
                'method': 'PATCH',
                'url': '/api/blog/webhook/update/',
                'authentication': 'Bearer token in Authorization header',
                'required_fields': ['url'],
                'optional_fields': [
                    'title', 'seo_title', 'lead', 'introduction', 'content',
                    'status', 'cta_id',
                    'cover_image_url', 'cover_image_alt_text',
                    'featured_image_url', 'featured_image_alt_text'
                ]
            }
        },
        'available_ctas': available_ctas
    })
