# app/services/content_page_service.py
"""
Content Page Service

Business logic for managing content pages with platform defaults
and vendor-specific overrides.

Lookup Strategy:
1. Check for vendor-specific override (vendor_id + slug + published)
2. If not found, check for platform default (slug + published)
3. If neither exists, return None

This allows:
- Platform admin to create default content for all vendors
- Vendors to override specific pages with custom content
- Fallback to platform defaults when vendor hasn't customized
"""

import logging
from datetime import UTC, datetime

from sqlalchemy import and_
from sqlalchemy.orm import Session

from app.exceptions.content_page import (
    ContentPageNotFoundException,
    UnauthorizedContentPageAccessException,
)
from models.database.content_page import ContentPage

logger = logging.getLogger(__name__)


class ContentPageService:
    """Service for content page operations."""

    @staticmethod
    def get_page_for_vendor(
        db: Session,
        slug: str,
        vendor_id: int | None = None,
        include_unpublished: bool = False,
    ) -> ContentPage | None:
        """
        Get content page for a vendor with fallback to platform default.

        Lookup order:
        1. Vendor-specific override (vendor_id + slug)
        2. Platform default (vendor_id=NULL + slug)

        Args:
            db: Database session
            slug: Page slug (about, faq, contact, etc.)
            vendor_id: Vendor ID (None for platform defaults only)
            include_unpublished: Include draft pages (for preview)

        Returns:
            ContentPage or None
        """
        filters = [ContentPage.slug == slug]

        if not include_unpublished:
            filters.append(ContentPage.is_published == True)

        # Try vendor-specific override first
        if vendor_id:
            vendor_page = (
                db.query(ContentPage)
                .filter(and_(ContentPage.vendor_id == vendor_id, *filters))
                .first()
            )

            if vendor_page:
                logger.debug(
                    f"Found vendor-specific page: {slug} for vendor_id={vendor_id}"
                )
                return vendor_page

        # Fallback to platform default
        platform_page = (
            db.query(ContentPage)
            .filter(and_(ContentPage.vendor_id == None, *filters))
            .first()
        )

        if platform_page:
            logger.debug(f"Using platform default page: {slug}")
        else:
            logger.debug(f"No page found for slug: {slug}")

        return platform_page

    @staticmethod
    def list_pages_for_vendor(
        db: Session,
        vendor_id: int | None = None,
        include_unpublished: bool = False,
        footer_only: bool = False,
        header_only: bool = False,
    ) -> list[ContentPage]:
        """
        List all available pages for a vendor (includes vendor overrides + platform defaults).

        Merges vendor-specific overrides with platform defaults, prioritizing vendor overrides.

        Args:
            db: Database session
            vendor_id: Vendor ID (None for platform pages only)
            include_unpublished: Include draft pages
            footer_only: Only pages marked for footer display
            header_only: Only pages marked for header display

        Returns:
            List of ContentPage objects
        """
        filters = []

        if not include_unpublished:
            filters.append(ContentPage.is_published == True)

        if footer_only:
            filters.append(ContentPage.show_in_footer == True)

        if header_only:
            filters.append(ContentPage.show_in_header == True)

        # Get vendor-specific pages
        vendor_pages = []
        if vendor_id:
            vendor_pages = (
                db.query(ContentPage)
                .filter(and_(ContentPage.vendor_id == vendor_id, *filters))
                .order_by(ContentPage.display_order, ContentPage.title)
                .all()
            )

        # Get platform defaults
        platform_pages = (
            db.query(ContentPage)
            .filter(and_(ContentPage.vendor_id == None, *filters))
            .order_by(ContentPage.display_order, ContentPage.title)
            .all()
        )

        # Merge: vendor overrides take precedence
        vendor_slugs = {page.slug for page in vendor_pages}
        all_pages = vendor_pages + [
            page for page in platform_pages if page.slug not in vendor_slugs
        ]

        # Sort by display_order
        all_pages.sort(key=lambda p: (p.display_order, p.title))

        return all_pages

    @staticmethod
    def create_page(
        db: Session,
        slug: str,
        title: str,
        content: str,
        vendor_id: int | None = None,
        content_format: str = "html",
        template: str = "default",
        meta_description: str | None = None,
        meta_keywords: str | None = None,
        is_published: bool = False,
        show_in_footer: bool = True,
        show_in_header: bool = False,
        display_order: int = 0,
        created_by: int | None = None,
    ) -> ContentPage:
        """
        Create a new content page.

        Args:
            db: Database session
            slug: URL-safe identifier
            title: Page title
            content: HTML or Markdown content
            vendor_id: Vendor ID (None for platform default)
            content_format: "html" or "markdown"
            template: Template name for homepage/landing pages (default, minimal, modern, etc.)
            meta_description: SEO description
            meta_keywords: SEO keywords
            is_published: Publish immediately
            show_in_footer: Show in footer navigation
            show_in_header: Show in header navigation
            display_order: Sort order
            created_by: User ID who created it

        Returns:
            Created ContentPage
        """
        page = ContentPage(
            vendor_id=vendor_id,
            slug=slug,
            title=title,
            content=content,
            content_format=content_format,
            template=template,
            meta_description=meta_description,
            meta_keywords=meta_keywords,
            is_published=is_published,
            published_at=datetime.now(UTC) if is_published else None,
            show_in_footer=show_in_footer,
            show_in_header=show_in_header,
            display_order=display_order,
            created_by=created_by,
            updated_by=created_by,
        )

        db.add(page)
        db.flush()
        db.refresh(page)

        logger.info(
            f"Created content page: {slug} (vendor_id={vendor_id}, id={page.id})"
        )
        return page

    @staticmethod
    def update_page(
        db: Session,
        page_id: int,
        title: str | None = None,
        content: str | None = None,
        content_format: str | None = None,
        template: str | None = None,
        meta_description: str | None = None,
        meta_keywords: str | None = None,
        is_published: bool | None = None,
        show_in_footer: bool | None = None,
        show_in_header: bool | None = None,
        display_order: int | None = None,
        updated_by: int | None = None,
    ) -> ContentPage | None:
        """
        Update an existing content page.

        Args:
            db: Database session
            page_id: Page ID
            title: New title
            content: New content
            content_format: New format
            template: New template name
            meta_description: New SEO description
            meta_keywords: New SEO keywords
            is_published: New publish status
            show_in_footer: New footer visibility
            show_in_header: New header visibility
            display_order: New sort order
            updated_by: User ID who updated it

        Returns:
            Updated ContentPage or None if not found
        """
        page = db.query(ContentPage).filter(ContentPage.id == page_id).first()

        if not page:
            logger.warning(f"Content page not found: id={page_id}")
            return None

        # Update fields if provided
        if title is not None:
            page.title = title
        if content is not None:
            page.content = content
        if content_format is not None:
            page.content_format = content_format
        if template is not None:
            page.template = template
        if meta_description is not None:
            page.meta_description = meta_description
        if meta_keywords is not None:
            page.meta_keywords = meta_keywords
        if is_published is not None:
            page.is_published = is_published
            if is_published and not page.published_at:
                page.published_at = datetime.now(UTC)
        if show_in_footer is not None:
            page.show_in_footer = show_in_footer
        if show_in_header is not None:
            page.show_in_header = show_in_header
        if display_order is not None:
            page.display_order = display_order
        if updated_by is not None:
            page.updated_by = updated_by

        db.flush()
        db.refresh(page)

        logger.info(f"Updated content page: id={page_id}, slug={page.slug}")
        return page

    @staticmethod
    def delete_page(db: Session, page_id: int) -> bool:
        """
        Delete a content page.

        Args:
            db: Database session
            page_id: Page ID

        Returns:
            True if deleted, False if not found
        """
        page = db.query(ContentPage).filter(ContentPage.id == page_id).first()

        if not page:
            logger.warning(f"Content page not found for deletion: id={page_id}")
            return False

        db.delete(page)

        logger.info(f"Deleted content page: id={page_id}, slug={page.slug}")
        return True

    @staticmethod
    def get_page_by_id(db: Session, page_id: int) -> ContentPage | None:
        """Get content page by ID."""
        return db.query(ContentPage).filter(ContentPage.id == page_id).first()

    @staticmethod
    def get_page_by_id_or_raise(db: Session, page_id: int) -> ContentPage:
        """
        Get content page by ID or raise ContentPageNotFoundException.

        Args:
            db: Database session
            page_id: Page ID

        Returns:
            ContentPage

        Raises:
            ContentPageNotFoundException: If page not found
        """
        page = db.query(ContentPage).filter(ContentPage.id == page_id).first()
        if not page:
            raise ContentPageNotFoundException(identifier=page_id)
        return page

    @staticmethod
    def get_page_for_vendor_or_raise(
        db: Session,
        slug: str,
        vendor_id: int | None = None,
        include_unpublished: bool = False,
    ) -> ContentPage:
        """
        Get content page for a vendor with fallback to platform default.
        Raises ContentPageNotFoundException if not found.

        Args:
            db: Database session
            slug: Page slug
            vendor_id: Vendor ID
            include_unpublished: Include draft pages

        Returns:
            ContentPage

        Raises:
            ContentPageNotFoundException: If page not found
        """
        page = ContentPageService.get_page_for_vendor(
            db, slug=slug, vendor_id=vendor_id, include_unpublished=include_unpublished
        )
        if not page:
            raise ContentPageNotFoundException(identifier=slug)
        return page

    @staticmethod
    def update_page_or_raise(
        db: Session,
        page_id: int,
        title: str | None = None,
        content: str | None = None,
        content_format: str | None = None,
        template: str | None = None,
        meta_description: str | None = None,
        meta_keywords: str | None = None,
        is_published: bool | None = None,
        show_in_footer: bool | None = None,
        show_in_header: bool | None = None,
        display_order: int | None = None,
        updated_by: int | None = None,
    ) -> ContentPage:
        """
        Update an existing content page or raise exception.

        Raises:
            ContentPageNotFoundException: If page not found
        """
        page = ContentPageService.update_page(
            db,
            page_id=page_id,
            title=title,
            content=content,
            content_format=content_format,
            template=template,
            meta_description=meta_description,
            meta_keywords=meta_keywords,
            is_published=is_published,
            show_in_footer=show_in_footer,
            show_in_header=show_in_header,
            display_order=display_order,
            updated_by=updated_by,
        )
        if not page:
            raise ContentPageNotFoundException(identifier=page_id)
        return page

    @staticmethod
    def delete_page_or_raise(db: Session, page_id: int) -> None:
        """
        Delete a content page or raise exception.

        Raises:
            ContentPageNotFoundException: If page not found
        """
        success = ContentPageService.delete_page(db, page_id)
        if not success:
            raise ContentPageNotFoundException(identifier=page_id)

    @staticmethod
    def update_vendor_page(
        db: Session,
        page_id: int,
        vendor_id: int,
        title: str | None = None,
        content: str | None = None,
        content_format: str | None = None,
        meta_description: str | None = None,
        meta_keywords: str | None = None,
        is_published: bool | None = None,
        show_in_footer: bool | None = None,
        show_in_header: bool | None = None,
        display_order: int | None = None,
        updated_by: int | None = None,
    ) -> ContentPage:
        """
        Update a vendor-specific content page with ownership check.

        Args:
            db: Database session
            page_id: Page ID
            vendor_id: Vendor ID (for ownership verification)
            ... other fields

        Returns:
            Updated ContentPage

        Raises:
            ContentPageNotFoundException: If page not found
            UnauthorizedContentPageAccessException: If page doesn't belong to vendor
        """
        page = ContentPageService.get_page_by_id_or_raise(db, page_id)

        if page.vendor_id != vendor_id:
            raise UnauthorizedContentPageAccessException(action="edit")

        return ContentPageService.update_page_or_raise(
            db,
            page_id=page_id,
            title=title,
            content=content,
            content_format=content_format,
            meta_description=meta_description,
            meta_keywords=meta_keywords,
            is_published=is_published,
            show_in_footer=show_in_footer,
            show_in_header=show_in_header,
            display_order=display_order,
            updated_by=updated_by,
        )

    @staticmethod
    def delete_vendor_page(db: Session, page_id: int, vendor_id: int) -> None:
        """
        Delete a vendor-specific content page with ownership check.

        Args:
            db: Database session
            page_id: Page ID
            vendor_id: Vendor ID (for ownership verification)

        Raises:
            ContentPageNotFoundException: If page not found
            UnauthorizedContentPageAccessException: If page doesn't belong to vendor
        """
        page = ContentPageService.get_page_by_id_or_raise(db, page_id)

        if page.vendor_id != vendor_id:
            raise UnauthorizedContentPageAccessException(action="delete")

        ContentPageService.delete_page_or_raise(db, page_id)

    @staticmethod
    def list_all_pages(
        db: Session,
        vendor_id: int | None = None,
        include_unpublished: bool = False,
    ) -> list[ContentPage]:
        """
        List all content pages (platform defaults and vendor overrides).

        Args:
            db: Database session
            vendor_id: Optional filter by vendor ID
            include_unpublished: Include draft pages

        Returns:
            List of ContentPage objects
        """
        filters = []

        if vendor_id:
            filters.append(ContentPage.vendor_id == vendor_id)

        if not include_unpublished:
            filters.append(ContentPage.is_published == True)

        return (
            db.query(ContentPage)
            .filter(and_(*filters) if filters else True)
            .order_by(ContentPage.vendor_id, ContentPage.display_order, ContentPage.title)
            .all()
        )

    @staticmethod
    def list_all_vendor_pages(
        db: Session, vendor_id: int, include_unpublished: bool = False
    ) -> list[ContentPage]:
        """
        List only vendor-specific pages (no platform defaults).

        Args:
            db: Database session
            vendor_id: Vendor ID
            include_unpublished: Include draft pages

        Returns:
            List of vendor-specific ContentPage objects
        """
        filters = [ContentPage.vendor_id == vendor_id]

        if not include_unpublished:
            filters.append(ContentPage.is_published == True)

        return (
            db.query(ContentPage)
            .filter(and_(*filters))
            .order_by(ContentPage.display_order, ContentPage.title)
            .all()
        )

    @staticmethod
    def list_all_platform_pages(
        db: Session, include_unpublished: bool = False
    ) -> list[ContentPage]:
        """
        List only platform default pages.

        Args:
            db: Database session
            include_unpublished: Include draft pages

        Returns:
            List of platform default ContentPage objects
        """
        filters = [ContentPage.vendor_id == None]

        if not include_unpublished:
            filters.append(ContentPage.is_published == True)

        return (
            db.query(ContentPage)
            .filter(and_(*filters))
            .order_by(ContentPage.display_order, ContentPage.title)
            .all()
        )


# Singleton instance
content_page_service = ContentPageService()
