import json
import logging
import math
import traceback
from typing import Optional

from fastapi import APIRouter, Depends, Request
from pydantic import BaseModel, ValidationError
from fastapi.responses import JSONResponse
from sqlalchemy import and_, or_, select, func
from sqlalchemy.ext.asyncio import AsyncSession

from app.db.session import get_db
from app.models.tattoo_style import TattooStyle
from app.models.category import Category
from app.schemas.tattoo_style import TattooStyleByCategoryQueryParams, TattooStyleIdRequest
from app.core.security import validate_token
from app.api.v1.category_api import decrypt_data
from app.utils.responses import custom_response
from app.core.logging import logger

router = APIRouter()

# ---------- Get Tattoo Styles by Category (POST) ----------
@router.post("/category/tattoo_style")
async def get_tattoo_styles_by_category(
    request: Request,
    db: AsyncSession = Depends(get_db),
    token_data: dict = Depends(validate_token)
):
    """
    Get tattoo styles for a specific category with search and pagination (POST with encrypted data).
    """
    # Log the start of the request
    logger.info("Received request for /category/tattoo_style endpoint.")
    
    try:
        # Step 1: Authorization check
        if token_data.get('code') != 200:
            # Log a warning for an unauthorized access attempt
            logger.warning(f"Unauthorized access attempt to /category/tattoo_style. Message: {token_data.get('message', 'N/A')}")
            return custom_response(code=token_data.get('code', 500), message=token_data.get('message', 'Unauthorized'))

        # Step 2: Read raw encrypted data
        try:
            encrypted_data = await request.body()
            # Log the encrypted data for debugging purposes
            logger.debug(f"Encrypted request body received: {encrypted_data}")
        except Exception as e:
            # Log an error if reading the body fails
            logger.error(f"Failed to read request body: {str(e)}")
            return custom_response(code=400, message=f"Failed to read request body: {str(e)}")

        # Step 3: Decrypt data
        decrypted_data = decrypt_data(encrypted_data)

        # Step 4: Parse decrypted JSON and validate schema
        try:
            # decrypted_json = json.loads(decrypted_data)
            decrypted_json = decrypted_data
            # Log the decrypted payload for debugging
            logger.debug(f"Decrypted JSON request payload: {decrypted_json}")
            params = TattooStyleByCategoryQueryParams.model_validate(decrypted_json)
        except json.JSONDecodeError:
            # Log a warning for malformed JSON
            logger.warning(f"Invalid JSON after decryption for /category/tattoo_style. Payload: {decrypted_data}")
            return custom_response(code=400, message="Invalid JSON after decryption")
        except ValidationError as ve:
            # Log a warning for validation errors
            logger.warning(f"Validation failed for tattoo style query. Errors: {ve.errors()}")
            return custom_response(code=422, data=ve.errors(), message="Validation failed")

        # Log the request parameters
        logger.info(f"Fetching tattoo styles for category_id={params.category_id}, page={params.page}, limit={params.limit}, search='{params.search}'")
        
        # Step 5: Check if category exists
        category_stmt = select(Category).where(
            and_(
                Category.category_id == params.category_id,
                Category.is_active.is_(True)
            )
        )
        category_result = await db.execute(category_stmt)
        category = category_result.scalar_one_or_none()

        if not category:
            # Log a warning if the category is not found
            logger.warning(f"Category with ID {params.category_id} not found or is inactive.")
            return custom_response(code=404, message="Category not found")

        # Step 6: Build query conditions dynamically
        conditions = [
            TattooStyle.category_id == params.category_id,
            TattooStyle.is_active.is_(True)
        ]

        if params.search:
            search_term = f"%{params.search}%"
            conditions.append(or_(
                TattooStyle.name.ilike(search_term)
            ))

        # Construct a single statement for both data and total count
        stmt = (
            select(TattooStyle, func.count().over().label('total_count'))
            .where(and_(*conditions))
            .order_by(TattooStyle.name.asc())
            .offset((params.page - 1) * params.limit)
            .limit(params.limit)
        )
        
        result = await db.execute(stmt)
        rows = result.all()
        
        total_count = rows[0].total_count if rows else 0
        total_pages = math.ceil(total_count / params.limit) if total_count > 0 else 0
        has_next = params.page < total_pages

        items = [
            {
                "style_id": style.style_id,
                "name": style.name,
                "description": style.description,
                "category_id": style.category_id,
                "thumbnail_url": style.thumbnail_url,
                "is_free": style.is_free,
                "is_active": style.is_active,
                "sort_order": style.sort_order
            }
            for style in [row.TattooStyle for row in rows]
        ]
        
        if not items:
            # Log a warning if no items are found for the query
            logger.warning(f"No tattoo styles found for category {params.category_id}.")
            data = {'details': items,"page":params.page, "limit":params.limit, "total_count":total_count, "total_pages":total_pages, "has_next":has_next}

            return custom_response(code=404, data=data, message="No tattoo styles found for this category")

        # Log a success message
        logger.info(f"Successfully retrieved {len(items)} tattoo styles for category {params.category_id}.")
        
        data = {'details': items,"page":params.page, "limit":params.limit, "total_count":total_count, "total_pages":total_pages, "has_next":has_next}
        
        return custom_response(200, data=data, message="Success")
    
    except Exception as e:
        # Log a critical error with traceback
        logger.error(f"Internal server error in /category/tattoo_style: {str(e)}", exc_info=True)
        return custom_response(code=500, message=f"Internal server error: {str(e)}")


# ---------- Get a specific tattoo style by ID (POST) ----------
@router.post("/tattoo_style")
async def get_tattoo_style_by_id(
    request: Request,
    db: AsyncSession = Depends(get_db),
    token_data: dict = Depends(validate_token)
):
    # Log the start of the request
    logger.info("Received request for /tattoo_style endpoint.")
    
    try:
        # Step 1: Authorization check
        if token_data.get('code') != 200:
            # Log a warning for an unauthorized access attempt
            logger.warning(f"Unauthorized access attempt to /tattoo_style. Message: {token_data.get('message', 'N/A')}")
            return custom_response(code=token_data.get('code', 500), message=token_data.get('message', 'Unauthorized'))

        # Step 2: Read raw encrypted data
        try:
            encrypted_data = await request.body()
            # Log the encrypted data for debugging
            logger.debug(f"Encrypted request body received for /tattoo_style: {encrypted_data}")
        except Exception as e:
            # Log an error if reading the body fails
            logger.error(f"Failed to read request body for /tattoo_style: {str(e)}")
            return custom_response(code=400, message=f"Failed to read request body: {str(e)}")

        # Step 3: Decrypt data
        decrypted_data = decrypt_data(encrypted_data)

        # Step 4: Parse decrypted JSON and validate schema
        try:
            decrypted_json = json.loads(decrypted_data)
            # Log the decrypted payload
            logger.debug(f"Decrypted JSON payload for /tattoo_style: {decrypted_json}")
            params = TattooStyleIdRequest.model_validate(decrypted_json)
        except json.JSONDecodeError:
            # Log a warning for malformed JSON
            logger.warning(f"Invalid JSON after decryption for /tattoo_style. Payload: {decrypted_data}")
            return custom_response(code=400, message="Invalid JSON after decryption")
        except ValidationError as ve:
            # Log a warning for validation errors
            logger.warning(f"Validation failed for tattoo style ID request. Errors: {ve.errors()}")
            return custom_response(code=422, data=ve.errors(), message="Validation failed")
            
        # Log the request parameter
        logger.info(f"Fetching tattoo style with ID: {params.style_id}")
        
        # Step 5: DB query logic
        stmt = select(TattooStyle).where(
            and_(
                TattooStyle.style_id == params.style_id,
                TattooStyle.is_active.is_(True)
            )
        )
        result = await db.execute(stmt)
        style = result.scalar_one_or_none()
        
        if not style:
            # Log a warning if the style is not found
            logger.warning(f"Tattoo style with ID {params.style_id} not found or is inactive.")
            return custom_response(code=404, message="Tattoo style not found")

        # Step 6: Serialize and return response
        style_data = {
            "style_id": style.style_id,
            "name": style.name,
            "description": style.description,
            "category_id": style.category_id,
            "thumbnail_url": style.thumbnail_url,
            "is_active": style.is_active,
            "sort_order": style.sort_order
        }

        # Log a success message
        logger.info(f"Successfully retrieved tattoo style with ID: {params.style_id}.")
        return custom_response(200, data=style_data, message="Success")
    
    except Exception as e:
        # Log a critical error with traceback
        logger.error(f"Internal server error in /tattoo_style: {str(e)}", exc_info=True)
        return custom_response(code=500, message=f"Internal server error: {str(e)}")
