from fastapi import APIRouter, Depends, HTTPException, status, Query, Request, UploadFile, File
from sqlalchemy.orm import Session, joinedload
from sqlalchemy import or_, func, case
from typing import Optional, List
from datetime import datetime
import json
import re
import os
import uuid
import aiofiles
from pathlib import Path
from ...shared.config.database import get_db
from ...shared.config.logging_config import get_logger
from ...security.middleware.auth import get_current_user, get_current_user_optional, authorize_roles
from ...auth.models.user import User
from ..models.blog import BlogPost
from ..schemas.blog import BlogPostCreate, BlogPostUpdate, BlogPostResponse, BlogPostListResponse
from ...shared.utils.response_helpers import success_response

logger = get_logger(__name__)
router = APIRouter(prefix='/blog', tags=['blog'])

def normalize_image_url(image_url: str, base_url: str) -> str:
    if not image_url:
        return image_url
    if image_url.startswith('http://') or image_url.startswith('https://'):
        return image_url
    if image_url.startswith('/'):
        return f'{base_url}{image_url}'
    return f'{base_url}/{image_url}'

def get_base_url(request: Request) -> str:
    return os.getenv('SERVER_URL') or f'http://{request.headers.get("host", "localhost:8000")}'

def generate_slug(title: str) -> str:
    """Generate a URL-friendly slug from title"""
    slug = title.lower()
    slug = re.sub(r'[^\w\s-]', '', slug)
    slug = re.sub(r'[-\s]+', '-', slug)
    return slug.strip('-')

@router.get('/', response_model=dict)
async def get_blog_posts(
    request: Request,
    page: int = Query(1, ge=1),
    limit: int = Query(10, ge=1, le=50),
    search: Optional[str] = Query(None),
    tag: Optional[str] = Query(None),
    published_only: bool = Query(True),
    db: Session = Depends(get_db)
):
    """Get list of blog posts (public endpoint)"""
    try:
        query = db.query(BlogPost)
        
        if published_only:
            query = query.filter(BlogPost.is_published == True)
            # Only show posts with published_at in the past or null
            query = query.filter(
                or_(
                    BlogPost.published_at <= datetime.utcnow(),
                    BlogPost.published_at.is_(None)
                )
            )
        
        if search:
            search_term = f"%{search}%"
            query = query.filter(
                or_(
                    BlogPost.title.ilike(search_term),
                    BlogPost.excerpt.ilike(search_term),
                    BlogPost.content.ilike(search_term)
                )
            )
        
        if tag:
            query = query.filter(BlogPost.tags.ilike(f'%"{tag}"%'))
        
        total = query.count()
        # Order by published_at descending, handling null values
        # MySQL doesn't support NULLS LAST, so we use a CASE statement to put NULLs last
        # Eager load author relationship to avoid N+1 queries
        posts = query.options(joinedload(BlogPost.author)).order_by(
            case((BlogPost.published_at.is_(None), 1), else_=0),
            BlogPost.published_at.desc(),
            BlogPost.created_at.desc()
        ).offset((page - 1) * limit).limit(limit).all()
        
        base_url = get_base_url(request)
        result = []
        for post in posts:
            tags_list = json.loads(post.tags) if post.tags else []
            post_dict = {
                'id': post.id,
                'title': post.title,
                'slug': post.slug,
                'excerpt': post.excerpt,
                'featured_image': normalize_image_url(post.featured_image, base_url) if post.featured_image else None,
                'author_id': post.author_id,
                'author_name': post.author.full_name if post.author else None,
                'published_at': post.published_at.isoformat() if post.published_at else None,
                'views_count': post.views_count,
                'tags': tags_list,
                'created_at': post.created_at.isoformat()
            }
            result.append(post_dict)
        
        # Get all unique tags from all published posts (for filter display)
        all_tags_query = db.query(BlogPost).filter(BlogPost.is_published == True)
        if published_only:
            all_tags_query = all_tags_query.filter(
                or_(
                    BlogPost.published_at <= datetime.utcnow(),
                    BlogPost.published_at.is_(None)
                )
            )
        all_posts_for_tags = all_tags_query.all()
        all_unique_tags = set()
        for post in all_posts_for_tags:
            if post.tags:
                try:
                    tags_list = json.loads(post.tags)
                    all_unique_tags.update(tags_list)
                except:
                    pass
        
        return success_response({
            'posts': result,
            'pagination': {
                'page': page,
                'limit': limit,
                'total': total,
                'pages': (total + limit - 1) // limit
            },
            'all_tags': sorted(list(all_unique_tags))
        })
    except Exception as e:
        logger.error(f"Error in get_blog_posts: {str(e)}", exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/{slug}', response_model=dict)
async def get_blog_post_by_slug(
    slug: str,
    request: Request,
    current_user: Optional[User] = Depends(get_current_user_optional),
    db: Session = Depends(get_db)
):
    """Get a single blog post by slug (public endpoint)"""
    try:
        post = db.query(BlogPost).filter(BlogPost.slug == slug).first()
        if not post:
            raise HTTPException(status_code=404, detail='Blog post not found')
        
        # Only show published posts to non-admin users
        # Check if user is admin
        from ...auth.models.role import Role
        is_admin = False
        if current_user:
            role = db.query(Role).filter(Role.id == current_user.role_id).first()
            is_admin = role and role.name == 'admin'
        
        if not is_admin and (not post.is_published or (post.published_at and post.published_at > datetime.utcnow())):
            raise HTTPException(status_code=404, detail='Blog post not found')
        
        # Increment views count
        post.views_count += 1
        db.commit()
        
        base_url = get_base_url(request)
        tags_list = json.loads(post.tags) if post.tags else []
        sections_data = post.sections if post.sections else []
        
        # Filter sections by is_visible if the field exists
        if sections_data:
            sections_data = [
                section for section in sections_data 
                if section.get('is_visible', True)  # Default to True if not specified
            ]
        
        post_dict = {
            'id': post.id,
            'title': post.title,
            'slug': post.slug,
            'excerpt': post.excerpt,
            'content': post.content,
            'featured_image': normalize_image_url(post.featured_image, base_url) if post.featured_image else None,
            'author_id': post.author_id,
            'author_name': post.author.full_name if post.author else None,
            'published_at': post.published_at.isoformat() if post.published_at else None,
            'is_published': post.is_published,
            'views_count': post.views_count,
            'tags': tags_list,
            'meta_title': post.meta_title,
            'meta_description': post.meta_description,
            'meta_keywords': post.meta_keywords,
            'sections': sections_data,
            'created_at': post.created_at.isoformat(),
            'updated_at': post.updated_at.isoformat()
        }
        
        return success_response({'post': post_dict})
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/admin/posts', response_model=dict)
async def get_all_blog_posts_admin(
    request: Request,
    page: int = Query(1, ge=1),
    limit: int = Query(20, ge=1, le=100),
    search: Optional[str] = Query(None),
    published: Optional[bool] = Query(None),
    current_user: User = Depends(authorize_roles('admin')),
    db: Session = Depends(get_db)
):
    """Get all blog posts for admin (includes unpublished)"""
    try:
        query = db.query(BlogPost)
        
        if search:
            search_term = f"%{search}%"
            query = query.filter(
                or_(
                    BlogPost.title.ilike(search_term),
                    BlogPost.excerpt.ilike(search_term),
                    BlogPost.content.ilike(search_term)
                )
            )
        
        if published is not None:
            query = query.filter(BlogPost.is_published == published)
        
        total = query.count()
        posts = query.order_by(BlogPost.created_at.desc()).offset((page - 1) * limit).limit(limit).all()
        
        base_url = get_base_url(request)
        result = []
        for post in posts:
            tags_list = json.loads(post.tags) if post.tags else []
            post_dict = {
                'id': post.id,
                'title': post.title,
                'slug': post.slug,
                'excerpt': post.excerpt,
                'featured_image': normalize_image_url(post.featured_image, base_url) if post.featured_image else None,
                'author_id': post.author_id,
                'author_name': post.author.full_name if post.author else None,
                'published_at': post.published_at.isoformat() if post.published_at else None,
                'is_published': post.is_published,
                'views_count': post.views_count,
                'tags': tags_list,
                'created_at': post.created_at.isoformat(),
                'updated_at': post.updated_at.isoformat()
            }
            result.append(post_dict)
        
        return success_response({
            'posts': result,
            'pagination': {
                'page': page,
                'limit': limit,
                'total': total,
                'pages': (total + limit - 1) // limit
            }
        })
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/admin/posts/{id}', response_model=dict)
async def get_blog_post_admin(
    id: int,
    request: Request,
    current_user: User = Depends(authorize_roles('admin')),
    db: Session = Depends(get_db)
):
    """Get a single blog post by ID for admin"""
    try:
        post = db.query(BlogPost).filter(BlogPost.id == id).first()
        if not post:
            raise HTTPException(status_code=404, detail='Blog post not found')
        
        base_url = get_base_url(request)
        tags_list = json.loads(post.tags) if post.tags else []
        sections_data = post.sections if post.sections else []
        
        post_dict = {
            'id': post.id,
            'title': post.title,
            'slug': post.slug,
            'excerpt': post.excerpt,
            'content': post.content,
            'featured_image': normalize_image_url(post.featured_image, base_url) if post.featured_image else None,
            'author_id': post.author_id,
            'author_name': post.author.full_name if post.author else None,
            'published_at': post.published_at.isoformat() if post.published_at else None,
            'is_published': post.is_published,
            'views_count': post.views_count,
            'tags': tags_list,
            'meta_title': post.meta_title,
            'meta_description': post.meta_description,
            'meta_keywords': post.meta_keywords,
            'sections': sections_data,
            'created_at': post.created_at.isoformat(),
            'updated_at': post.updated_at.isoformat()
        }
        
        return success_response({'post': post_dict})
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.post('/admin/posts', response_model=dict, status_code=status.HTTP_201_CREATED)
async def create_blog_post(
    post_data: BlogPostCreate,
    request: Request,
    current_user: User = Depends(authorize_roles('admin')),
    db: Session = Depends(get_db)
):
    """Create a new blog post"""
    try:
        # Generate slug if not provided
        slug = post_data.slug or generate_slug(post_data.title)
        
        # Ensure slug is unique
        existing = db.query(BlogPost).filter(BlogPost.slug == slug).first()
        if existing:
            counter = 1
            base_slug = slug
            while existing:
                slug = f"{base_slug}-{counter}"
                existing = db.query(BlogPost).filter(BlogPost.slug == slug).first()
                counter += 1
        
        # Convert tags list to JSON string
        tags_json = json.dumps(post_data.tags) if post_data.tags else None
        
        # Set published_at if publishing
        published_at = post_data.published_at
        if post_data.is_published and not published_at:
            published_at = datetime.utcnow()
        
        # Handle sections - ensure it's a valid list or None
        sections_data = post_data.sections if post_data.sections else []
        
        post = BlogPost(
            title=post_data.title,
            slug=slug,
            excerpt=post_data.excerpt,
            content=post_data.content,
            featured_image=post_data.featured_image,
            author_id=current_user.id,
            tags=tags_json,
            meta_title=post_data.meta_title,
            meta_description=post_data.meta_description,
            meta_keywords=post_data.meta_keywords,
            is_published=post_data.is_published,
            published_at=published_at,
            sections=sections_data
        )
        
        db.add(post)
        db.commit()
        db.refresh(post)
        
        base_url = get_base_url(request)
        tags_list = json.loads(post.tags) if post.tags else []
        
        post_dict = {
            'id': post.id,
            'title': post.title,
            'slug': post.slug,
            'excerpt': post.excerpt,
            'content': post.content,
            'featured_image': normalize_image_url(post.featured_image, base_url) if post.featured_image else None,
            'author_id': post.author_id,
            'author_name': post.author.full_name if post.author else None,
            'published_at': post.published_at.isoformat() if post.published_at else None,
            'is_published': post.is_published,
            'views_count': post.views_count,
            'tags': tags_list,
            'meta_title': post.meta_title,
            'meta_description': post.meta_description,
            'meta_keywords': post.meta_keywords,
            'created_at': post.created_at.isoformat(),
            'updated_at': post.updated_at.isoformat()
        }
        
        return success_response({'post': post_dict}, message='Blog post created successfully')
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=str(e))

@router.put('/admin/posts/{id}', response_model=dict)
async def update_blog_post(
    id: int,
    post_data: BlogPostUpdate,
    request: Request,
    current_user: User = Depends(authorize_roles('admin')),
    db: Session = Depends(get_db)
):
    """Update a blog post"""
    try:
        post = db.query(BlogPost).filter(BlogPost.id == id).first()
        if not post:
            raise HTTPException(status_code=404, detail='Blog post not found')
        
        # Update fields
        if post_data.title is not None:
            post.title = post_data.title
            # Regenerate slug if title changed and slug wasn't explicitly provided
            if post_data.slug is None:
                post.slug = generate_slug(post_data.title)
                # Ensure slug is unique
                existing = db.query(BlogPost).filter(BlogPost.slug == post.slug, BlogPost.id != id).first()
                if existing:
                    counter = 1
                    base_slug = post.slug
                    while existing:
                        post.slug = f"{base_slug}-{counter}"
                        existing = db.query(BlogPost).filter(BlogPost.slug == post.slug, BlogPost.id != id).first()
                        counter += 1
        
        if post_data.slug is not None:
            # Ensure new slug is unique
            existing = db.query(BlogPost).filter(BlogPost.slug == post_data.slug, BlogPost.id != id).first()
            if existing:
                raise HTTPException(status_code=400, detail='Slug already exists')
            post.slug = post_data.slug
        
        if post_data.excerpt is not None:
            post.excerpt = post_data.excerpt
        if post_data.content is not None:
            post.content = post_data.content
        if post_data.featured_image is not None:
            post.featured_image = post_data.featured_image
        if post_data.tags is not None:
            post.tags = json.dumps(post_data.tags)
        if post_data.meta_title is not None:
            post.meta_title = post_data.meta_title
        if post_data.meta_description is not None:
            post.meta_description = post_data.meta_description
        if post_data.meta_keywords is not None:
            post.meta_keywords = post_data.meta_keywords
        if post_data.sections is not None:
            # Handle sections - ensure it's a valid JSON-serializable list
            if isinstance(post_data.sections, list):
                post.sections = post_data.sections if post_data.sections else []
            else:
                post.sections = []
        if post_data.is_published is not None:
            post.is_published = post_data.is_published
            # Set published_at if publishing for the first time
            if post_data.is_published and not post.published_at:
                post.published_at = datetime.utcnow()
        if post_data.published_at is not None:
            post.published_at = post_data.published_at
        
        db.commit()
        db.refresh(post)
        
        base_url = get_base_url(request)
        tags_list = json.loads(post.tags) if post.tags else []
        sections_data = post.sections if post.sections else []
        
        post_dict = {
            'id': post.id,
            'title': post.title,
            'slug': post.slug,
            'excerpt': post.excerpt,
            'content': post.content,
            'featured_image': normalize_image_url(post.featured_image, base_url) if post.featured_image else None,
            'author_id': post.author_id,
            'author_name': post.author.full_name if post.author else None,
            'published_at': post.published_at.isoformat() if post.published_at else None,
            'is_published': post.is_published,
            'views_count': post.views_count,
            'tags': tags_list,
            'meta_title': post.meta_title,
            'meta_description': post.meta_description,
            'meta_keywords': post.meta_keywords,
            'sections': sections_data,
            'created_at': post.created_at.isoformat(),
            'updated_at': post.updated_at.isoformat()
        }
        
        return success_response({'post': post_dict}, message='Blog post updated successfully')
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        import traceback
        error_detail = f'{str(e)}\n{traceback.format_exc()}'
        logger.error(f'Error updating blog post: {error_detail}')
        raise HTTPException(status_code=500, detail=f'Error updating blog post: {str(e)}')

@router.delete('/admin/posts/{id}', response_model=dict)
async def delete_blog_post(
    id: int,
    current_user: User = Depends(authorize_roles('admin')),
    db: Session = Depends(get_db)
):
    """Delete a blog post"""
    try:
        post = db.query(BlogPost).filter(BlogPost.id == id).first()
        if not post:
            raise HTTPException(status_code=404, detail='Blog post not found')
        
        db.delete(post)
        db.commit()
        
        return success_response(None, message='Blog post deleted successfully')
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=str(e))

@router.post('/admin/upload-image')
async def upload_blog_image(
    request: Request,
    image: UploadFile = File(...),
    current_user: User = Depends(authorize_roles('admin'))
):
    """Upload an image for blog posts"""
    try:
        if not image:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='No file provided')
        if not image.content_type or not image.content_type.startswith('image/'):
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f'File must be an image. Received: {image.content_type}')
        if not image.filename:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Filename is required')
        
        upload_dir = Path(__file__).parent.parent.parent.parent / 'uploads' / 'blog'
        upload_dir.mkdir(parents=True, exist_ok=True)
        
        ext = Path(image.filename).suffix or '.jpg'
        filename = f'blog-{uuid.uuid4()}{ext}'
        file_path = upload_dir / filename
        
        # Validate file
        from ...shared.config.settings import settings
        from ...shared.utils.file_validation import validate_uploaded_image
        max_size = settings.MAX_UPLOAD_SIZE
        
        content = await validate_uploaded_image(image, max_size)
        
        # Optimize image before saving
        from ...shared.utils.image_optimization import optimize_image_async, ImageType
        optimized_content, optimized_ext = await optimize_image_async(content, ImageType.BLOG)
        
        # Update filename extension if format changed
        if optimized_ext and optimized_ext != ext:
            filename = f'blog-{uuid.uuid4()}{optimized_ext}'
            file_path = upload_dir / filename
        
        async with aiofiles.open(file_path, 'wb') as f:
            await f.write(optimized_content)
        
        image_url = f'/uploads/blog/{filename}'
        base_url = get_base_url(request)
        full_url = normalize_image_url(image_url, base_url)
        
        return success_response({
            'image_url': image_url,
            'full_url': full_url
        }, message='Image uploaded successfully')
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error uploading image: {str(e)}')

@router.get('/admin/tags', response_model=dict)
async def get_all_tags(
    current_user: User = Depends(authorize_roles('admin')),
    db: Session = Depends(get_db)
):
    """Get all unique tags from all blog posts (admin only)"""
    try:
        all_posts = db.query(BlogPost).all()
        all_unique_tags = set()
        tag_usage = {}  # Track how many posts use each tag
        
        for post in all_posts:
            if post.tags:
                try:
                    tags_list = json.loads(post.tags)
                    for tag in tags_list:
                        all_unique_tags.add(tag)
                        tag_usage[tag] = tag_usage.get(tag, 0) + 1
                except:
                    pass
        
        tags_with_usage = [
            {'name': tag, 'usage_count': tag_usage.get(tag, 0)}
            for tag in sorted(all_unique_tags)
        ]
        
        return success_response({
            'tags': tags_with_usage,
            'total': len(tags_with_usage)
        })
    except Exception as e:
        logger.error(f"Error in get_all_tags: {str(e)}", exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.put('/admin/tags/rename', response_model=dict)
async def rename_tag(
    old_tag: str = Query(..., description='Old tag name'),
    new_tag: str = Query(..., description='New tag name'),
    current_user: User = Depends(authorize_roles('admin')),
    db: Session = Depends(get_db)
):
    """Rename a tag across all blog posts (admin only)"""
    try:
        if not old_tag or not new_tag:
            raise HTTPException(status_code=400, detail='Both old_tag and new_tag are required')
        
        if old_tag == new_tag:
            raise HTTPException(status_code=400, detail='Old and new tag names must be different')
        
        # Get all posts that contain the old tag
        all_posts = db.query(BlogPost).all()
        updated_count = 0
        
        for post in all_posts:
            if post.tags:
                try:
                    tags_list = json.loads(post.tags)
                    if old_tag in tags_list:
                        # Replace old tag with new tag
                        tags_list = [new_tag if tag == old_tag else tag for tag in tags_list]
                        post.tags = json.dumps(tags_list)
                        updated_count += 1
                except:
                    pass
        
        db.commit()
        
        return success_response({
            'old_tag': old_tag,
            'new_tag': new_tag,
            'updated_posts': updated_count
        }, message=f'Tag renamed successfully. Updated {updated_count} post(s).')
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        logger.error(f"Error in rename_tag: {str(e)}", exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

@router.delete('/admin/tags', response_model=dict)
async def delete_tag(
    tag: str = Query(..., description='Tag name to delete'),
    current_user: User = Depends(authorize_roles('admin')),
    db: Session = Depends(get_db)
):
    """Delete a tag from all blog posts (admin only)"""
    try:
        if not tag:
            raise HTTPException(status_code=400, detail='Tag name is required')
        
        # Get all posts that contain the tag
        all_posts = db.query(BlogPost).all()
        updated_count = 0
        
        for post in all_posts:
            if post.tags:
                try:
                    tags_list = json.loads(post.tags)
                    if tag in tags_list:
                        # Remove the tag
                        tags_list = [t for t in tags_list if t != tag]
                        post.tags = json.dumps(tags_list) if tags_list else None
                        updated_count += 1
                except:
                    pass
        
        db.commit()
        
        return success_response({
            'deleted_tag': tag,
            'updated_posts': updated_count
        }, message=f'Tag deleted successfully. Updated {updated_count} post(s).')
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        logger.error(f"Error in delete_tag: {str(e)}", exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))

