from django.db import transaction, IntegrityError, models
from rest_framework.response import Response
from rest_framework import status
from .models import CompanyHistory
import pytz
from django.utils import timezone
from .models import Company, OfficeHours, Holiday, SalesTiming, CompanyBotSettings
from apps.core.models import User
from datetime import datetime
from urllib.parse import urlparse
import logging

logger = logging.getLogger(__name__)


def normalize_website(url: str) -> str:
    """
    Normalize a website URL into a consistent comparable format.

    This function ensures all website URLs are stored and compared
    in a uniform way by removing protocol, 'www', trailing slashes,
    and converting to lowercase.

    Steps performed:
    1. Adds 'http://' if the URL has no scheme (so it can be parsed).
    2. Extracts the domain part (netloc) only.
    3. Removes 'www.' prefix if present.
    4. Converts to lowercase.
    5. Removes any trailing slashes.

    Example:
        >>> normalize_website("https://www.ABC.com/")
        'abc.com'
        >>> normalize_website("abc.com")
        'abc.com'
        >>> normalize_website("http://abc.com:8000/test")
        'abc.com:8000'

    Args:
        url (str): The website URL to normalize.

    Returns:
        str: Normalized domain (e.g. "abc.com") or the original value if empty/None.
    """
    if not url:
        return url

    parsed = urlparse(url if '://' in url else f'http://{url}')
    netloc = parsed.netloc.lower()
    if netloc.startswith('www.'):
        netloc = netloc[4:]
    return netloc.rstrip('/')

def parse_timezone_offset(timezone_string):
    """
    Parse timezone string and return a valid timezone
    Handles formats like: '(GMT-09:00) Alaska' or 'US/Pacific'

    Args:
        timezone_string (str): Timezone string from database

    Returns:
        timezone object
    """
    import re
    from datetime import timedelta

    # If it's already a valid pytz timezone, use it
    try:
        return pytz.timezone(timezone_string)
    except:
        pass

    # Try to extract GMT offset from string like "(GMT-09:00) Alaska"
    match = re.search(r'GMT([+-]\d{2}):(\d{2})', timezone_string)
    if match:
        hours = int(match.group(1))
        minutes = int(match.group(2))
        # Create a fixed offset timezone
        from datetime import timezone as dt_timezone
        offset = timedelta(hours=hours, minutes=minutes if hours >= 0 else -minutes)
        return dt_timezone(offset)

    # Default to UTC if we can't parse
    return pytz.UTC


def check_maya_office_hours_by_datetime(company_id, check_datetime):
    """
    Check if office is open at a specific datetime (for Maya bot)

    Args:
        company_id (int): Company ID
        check_datetime (datetime or str): Datetime to check
            Can be:
            - datetime object: datetime(2025, 10, 1, 14, 30)
            - string: "2025-10-01 14:30" or "2025-10-01 14:30:00"

    Returns:
        dict: {
            'is_office_hours': bool,   # True if office is open, False if closed
            'is_holiday': bool,        # True if it's a holiday, False if not
            'message': str,            # Simple message
            'error': str               # Error message if any, None otherwise
        }
    """

    try:
        # Get company by ID
        company = Company.objects.get(id=company_id, is_active=True)
    except Company.DoesNotExist:
        return {
            'is_office_hours': False,
            'is_holiday': False,
            'message': 'Company not found',
            'error': 'Company not found or inactive'
        }

    try:
        # Convert string to datetime if needed
        from datetime import datetime
        if isinstance(check_datetime, str):
            # Parse string like "2025-10-01 14:30" or "2025-10-01 14:30:00"
            try:
                check_datetime = datetime.strptime(check_datetime, "%Y-%m-%d %H:%M:%S")
            except ValueError:
                try:
                    check_datetime = datetime.strptime(check_datetime, "%Y-%m-%d %H:%M")
                except ValueError:
                    return {
                        'is_office_hours': False,
                        'is_holiday': False,
                        'message': 'Invalid datetime format',
                        'error': 'Datetime string must be in format "YYYY-MM-DD HH:MM" or "YYYY-MM-DD HH:MM:SS"'
                    }

        # Get company timezone
        company_timezone = parse_timezone_offset(company.timezone)

        # Convert check_datetime to company timezone
        if check_datetime.tzinfo is None:
            check_datetime = pytz.utc.localize(check_datetime)

        check_time_company = check_datetime.astimezone(company_timezone)
        check_date = check_time_company.date()
        check_time_only = check_time_company.time()
        check_day = check_time_company.strftime('%A').lower()

        # Check if it's a holiday
        holiday = Holiday.objects.filter(
            company=company,
            start_date__lte=check_date,
            end_date__gte=check_date,
            is_active=True
        ).first()

        if holiday:
            return {
                'is_office_hours': False,
                'is_holiday': True,
                'message': f'Holiday: {holiday.message}',
                'error': None
            }

        # Get office hours for the day
        try:
            office_hours = OfficeHours.objects.get(
                company=company,
                day=check_day
            )
        except OfficeHours.DoesNotExist:
            return {
                'is_office_hours': False,
                'is_holiday': False,
                'message': f'No office hours configured for {check_day.capitalize()}',
                'error': None
            }

        # Check if office is marked as open on this day
        if not office_hours.is_open:
            return {
                'is_office_hours': False,
                'is_holiday': False,
                'message': f'Office closed on {check_day.capitalize()}',
                'error': None
            }

        # Check if time is within office hours
        if office_hours.start_time <= check_time_only <= office_hours.end_time:
            return {
                'is_office_hours': True,
                'is_holiday': False,
                'message': 'Office is open',
                'error': None
            }
        else:
            return {
                'is_office_hours': False,
                'is_holiday': False,
                'message': 'Outside office hours',
                'error': None
            }

    except Exception as e:
        return {
            'is_office_hours': False,
            'is_holiday': False,
            'message': 'An error occurred',
            'error': str(e)
        }


def check_sales_time_by_phone(phone_number):
    """
    Check if it's sales time based on phone number
    """
    try:
        # Get bot by phone number
        bot = CompanyBotSettings.objects.get(phone_number=phone_number, is_active=True)
        company = bot.company

        company_timezone = pytz.timezone(company.timezone)
        current_time = timezone.now().astimezone(company_timezone)

        timezone_offset = int(current_time.utcoffset().total_seconds() / 3600)

        is_holiday = False
        today = current_time.date()
        
        holiday = Holiday.objects.filter(
            company=company,
            bots=bot,
            start_date__lte=today,
            end_date__gte=today,
            is_active=True
        ).first()

        if holiday:
            is_holiday = True

        sales_timing = SalesTiming.objects.filter(company=company, bot='hazel', is_active=True).first()

        if sales_timing:
            is_sales_time = sales_timing.start_time <= current_time.time() <= sales_timing.end_time
        else:
            is_sales_time = False

        # Get company users with phone numbers for advisor data
        company_users = User.objects.filter(
            companies=company,
            is_active=True
        ).select_related('profile')

        advisor_list = []
        for user in company_users:
            if hasattr(user, 'profile') and user.profile.phone_number:
                advisor_name = f"{user.profile.first_name} {user.profile.last_name}".strip()
                advisor_list.append(f"{advisor_name} have id {user.id}")
        
        advisor_data = ", ".join(advisor_list) if advisor_list else "No advisor data"

        return {
            'is_office_hours': is_sales_time,
            'is_holiday': is_holiday,
            'holiday_message': holiday.message if holiday else 'No holiday today.',
            'timezone_offset': timezone_offset,
            'company_name': company.name,
            'advisor_data': advisor_data,
        }

    except CompanyBotSettings.DoesNotExist:
        return {
            'is_office_hours': False,
            'is_holiday': False,
            'holiday_message': '',
            'timezone_offset': -7,
            'advisor_data': 'No advisor data',
        }
    except SalesTiming.DoesNotExist:
        return {
            'is_office_hours': False,
            'timezone_offset': -7,
            'is_holiday': False,
            'holiday_message': '',
            'advisor_data': 'No advisor data',
        }
    except Exception as e:
        return {
            'is_office_hours': False,
            'timezone_offset': -7,
            'is_holiday': False,
            'holiday_message': '',
            'advisor_data': 'No advisor data',
        }


def check_bot_sales_time(bot_phone_number):
    """
    Check if a bot is currently in sales time based on bot phone number

    Args:
        bot_phone_number (str): The bot's phone number to check

    Returns:
        dict: {
            'is_sales_time': bool,        # True if currently in sales time
            'error': str                  # Error message if something went wrong, None otherwise
        }
    """

    try:
        # 1. Find the bot by phone number
        try:
            bot = CompanyBotSettings.objects.select_related('company').get(
                phone_number=bot_phone_number,
                is_active=True
            )
        except CompanyBotSettings.DoesNotExist:
            return {
                'is_sales_time': False,
                'error': f'Bot with phone number {bot_phone_number} not found or inactive'
            }

        # 2. Get the company from the bot
        company = bot.company
        if not company.is_active:
            return {
                'is_sales_time': False,
                'error': f'Company {company.name} is inactive'
            }

        # 3. Check if bot is 'hazel' (only hazel has sales timing)
        if bot.bot_name != 'hazel':
            return {
                'is_sales_time': False,
                'error': f'Bot {bot.bot_name} does not have sales timing (only hazel does)'
            }

        # 4. Get company timezone and current time
        company_timezone = pytz.timezone(company.timezone)
        current_time_utc = timezone.now()
        current_time_company = current_time_utc.astimezone(company_timezone)
        current_time_only = current_time_company.time()

        # 5. Get sales timing for hazel bot in this company
        try:
            sales_timing = SalesTiming.objects.get(
                company=company,
                bot='hazel',
                is_active=True
            )
        except SalesTiming.DoesNotExist:
            return {
                'is_sales_time': False,
                'error': f'No sales timing configured for hazel bot in {company.name}'
            }

        # 6. Check if current time is within sales hours (like office 9-5)
        is_sales_time = sales_timing.start_time <= current_time_only <= sales_timing.end_time

        # 7. Return simple result
        return {
            'is_sales_time': is_sales_time,
            'error': None
        }

    except Exception as e:
        return {
            'is_sales_time': False,
            'error': f'Unexpected error: {str(e)}'
        }


def get_bot_working_info(bot_phone_number):
    """
    Check if bot is currently in working hours based on bot phone number

    Args:
        bot_phone_number (str): Bot phone number

    Returns:
        dict: {
            'is_off_hours': bool,  # false if holiday OR outside working hours, true if NOT holiday AND within working hours
            'timezone_offset': int,  # UTC offset like -7
            'transfer_instructions': str  # Transfer instructions for the company
        }
    """

    try:
        # Get bot by phone number
        bot = CompanyBotSettings.objects.select_related('company').get(
            phone_number=bot_phone_number,
            is_active=True
        )
        company = bot.company

        if not company.is_active:
            return {
                'is_off_hours': False,  # Company not found = off hours
                'timezone_offset': 0,
                'transfer_instructions': "Company is inactive"
            }

    except CompanyBotSettings.DoesNotExist:
        return {
            'is_off_hours': False,  # Bot not found = off hours
            'timezone_offset': 0,
            'transfer_instructions': "Bot not found"
        }

    # Get company timezone
    company_timezone = pytz.timezone(company.timezone)

    # Get current time in company timezone
    current_time_utc = timezone.now()
    current_time_company = current_time_utc.astimezone(company_timezone)

    # Calculate timezone offset
    timezone_offset = current_time_company.utcoffset().total_seconds() / 3600

    # Get current day name
    current_day = current_time_company.strftime('%A').lower()

    # Check if today is a holiday FOR THIS SPECIFIC BOT
    is_holiday = False
    today = current_time_company.date()

    # Check if this specific bot has any holidays today
    bot_holiday = Holiday.objects.filter(
        company=company,
        start_date__lte=today,
        end_date__gte=today,
        is_active=True,
        bots=bot  # This is the key difference - check holidays for this specific bot
    ).first()

    if bot_holiday:
        is_holiday = True

    # Get office hours for current day
    try:
        office_hours = OfficeHours.objects.get(
            company=company,
            day=current_day
        )
        # Check if office is open today
        if not office_hours.is_open:
            is_off_hours = False
        else:
            # Check if current time is within working hours
            current_time_only = current_time_company.time()
            is_within_hours = office_hours.start_time <= current_time_only <= office_hours.end_time
            is_off_hours = not is_holiday and is_within_hours
    except OfficeHours.DoesNotExist:
        # No office hours set for this day
        is_off_hours = False

    # Build transfer instructions
    transfer_instructions = ""

    # Get company users with phone numbers
    company_users = User.objects.filter(
        companies=company,
        is_active=True
    ).select_related('profile')

    # First statement: Loop through users with phone numbers
    user_instructions = []
    for user in company_users:
        if hasattr(user, 'profile') and user.profile.phone_number:
            advisor_name = f"{user.profile.first_name} {user.profile.last_name}".strip()
            if not advisor_name:
                advisor_name = user.username
            advisor_phone = user.profile.phone_number
            user_instructions.append(
                f"If the user wants to reach {advisor_name} then transfer to {advisor_phone};"
            )

    # Add user instructions to transfer_instructions
    if user_instructions:
        transfer_instructions += " ".join(user_instructions) + " "

    # Second statement: BDC number
    if company.bdc_number:
        transfer_instructions += (
            f"If the user wants to schedule a meeting then transfer the call to BDC {company.bdc_number} "
            f"and say 'I'll transfer you to our BDC'; "
        )

    # Third statement: Default message
    transfer_instructions += (
        "If the user asks for an advisor who is not in the list, say 'I'm sorry, we don't have an advisor by that name at our company. Would you like to connect with someone else?'"
    )

    return {
        'is_off_hours': is_off_hours,
        'timezone_offset': int(timezone_offset),
        'transfer_instructions': transfer_instructions
    }


def check_bot_holiday_by_phone(bot_phone_number):
    """
    Check if a bot phone number is currently experiencing a holiday.

    Args:
        bot_phone_number (str): The bot's phone number to check

    Returns:
        dict: {
            'is_holiday': bool,           # True if it's a holiday
            'message': str,               # Holiday message if applicable, None otherwise
            'error': str                  # Error message if something went wrong, None otherwise
        }
    """

    try:
        # 1. Find the bot by phone number
        try:
            bot = CompanyBotSettings.objects.select_related('company').get(
                phone_number=bot_phone_number,
                is_active=True
            )
        except CompanyBotSettings.DoesNotExist:
            return {
                'is_holiday': False,
                'message': None,
                'error': f'Bot with phone number {bot_phone_number} not found or inactive'
            }

        # 2. Get the company from the bot
        company = bot.company
        if not company.is_active:
            return {
                'is_holiday': False,
                'message': None,
                'error': f'Company {company.name} is inactive'
            }

        # 3. Get company timezone and current date
        company_timezone = pytz.timezone(company.timezone)
        current_time_utc = timezone.now()
        current_time_company = current_time_utc.astimezone(company_timezone)
        today = current_time_company.date()

        # 4. Check if today is a holiday for this company
        company_holidays = Holiday.objects.filter(
            company=company,
            start_date__lte=today,
            end_date__gte=today,
            is_active=True
        ).prefetch_related('bots')

        if not company_holidays.exists():
            # No holidays today
            return {
                'is_holiday': False,
                'message': None,
                'error': None
            }

        # 5. Check if any of the holidays apply to this specific bot
        for holiday in company_holidays:
            if holiday.bots.filter(id=bot.id).exists():
                # Found a holiday that applies to this bot
                return {
                    'is_holiday': True,
                    'message': holiday.message or f'Happy {holiday.name}!',
                    'error': None
                }

        # 6. Company has holidays but none apply to this bot
        return {
            'is_holiday': False,
            'message': None,
            'error': None
        }

    except Exception as e:
        return {
            'is_holiday': False,
            'message': None,
            'error': f'Unexpected error: {str(e)}'
        }


def apply_holidays_to_all_companies(request):
    """Apply current company's holidays to all user's companies with bot relationships"""
    user = request.user

    if not user.active_company:
        return Response({'error': 'No active company'}, status=status.HTTP_400_BAD_REQUEST)

    # Require explicit confirmation
    if not request.data.get('confirm', False):
        return Response({'error': 'You must confirm to apply to all companies'}, status=status.HTTP_400_BAD_REQUEST)

    # Other companies
    user_companies = user.companies.filter(is_active=True).exclude(id=user.active_company.id)
    if not user_companies.exists():
        return Response({'error': 'No other companies found'}, status=status.HTTP_400_BAD_REQUEST)

    # Permission filtering
    allowed_companies = []
    denied = []
    for company in user_companies:
        if user.has_company_permission('edit_company', company):
            allowed_companies.append(company)
        else:
            denied.append(company.name)

    if not allowed_companies:
        return Response({
            'error': 'No permission to edit any company',
            'denied_companies': denied,
        }, status=status.HTTP_403_FORBIDDEN)

    # Get source holidays with bot relationships
    current_holidays = Holiday.objects.filter(company=user.active_company).prefetch_related('bots')
    if not current_holidays.exists():
        return Response({'error': 'No holidays found in current company'}, status=status.HTTP_400_BAD_REQUEST)

    # Validate holidays
    validation_error = validate_holidays(current_holidays)
    if validation_error:
        return Response({'error': validation_error}, status=status.HTTP_400_BAD_REQUEST)

    try:
        with transaction.atomic():
            Holiday.objects.filter(company__in=allowed_companies).delete()

            source_bots = {
                bot.bot_name: bot
                for bot in CompanyBotSettings.objects.filter(company=user.active_company)
            }

            created_holidays = []

            for company in allowed_companies:

                target_bots = {
                    bot.bot_name: bot
                    for bot in CompanyBotSettings.objects.filter(company=company)
                }

                for bot_name, target_bot in target_bots.items():
                    if bot_name in source_bots:
                        source_bot = source_bots[bot_name]

                        target_bot.is_active = source_bot.is_active
                        target_bot.save()
                
                for holiday in current_holidays:
                    new_holiday = Holiday.objects.create(
                        company=company,
                        name=holiday.name,
                        message=holiday.message,
                        start_date=holiday.start_date,
                        end_date=holiday.end_date,
                        is_active=holiday.is_active
                    )

                    matching_bots = [
                        target_bots[source_bot.bot_name]
                        for source_bot in holiday.bots.all()
                        if source_bot.bot_name in target_bots
                    ]

                    if matching_bots:
                        new_holiday.bots.set(matching_bots)

                    created_holidays.append(new_holiday)

            # 3. History logs
            history_logs = []
            count = current_holidays.count()
            for company in allowed_companies:
                history_logs.append(CompanyHistory(
                    company=company,
                    updated_by=user,
                    action='bulk_replace',
                    model_name='Holiday',
                    object_id=None,
                    details=f"Replaced {count} holidays from {user.active_company.name} → {company.name}"
                ))

            CompanyHistory.objects.bulk_create(history_logs, batch_size=100)

        # Success response
        response_data = {
            'message': f'Holidays applied to {len(allowed_companies)} companies',
            'applied_count': len(allowed_companies),
            'total_companies': user_companies.count(),
            'skipped_companies': denied if denied else None,
            'source_company': user.active_company.name,
            'items_count': current_holidays.count()
        }

        return Response(response_data, status=status.HTTP_200_OK if not denied else status.HTTP_206_PARTIAL_CONTENT)

    except Exception as e:
        return Response({'error': f'Operation failed: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


def get_company_working_info(phone_number):
    """
    Check if company is currently in working hours based on phone number

    Args:
        phone_number (str): Company phone number

    Returns:
        dict: {
            'is_office_hours': bool,  # false if holiday OR outside working hours, true if NOT holiday AND within working hours
            'is_holiday': bool,  # true if today is a holiday, false otherwise
            'holiday_message': str,  # Holiday message if applicable, None otherwise
            'timezone_offset': int,  # UTC offset like -7
            'transfer_instructions': str  # Transfer instructions for the company
        }
    """
    companyBotSettings = None
    company = None

    try:
        # Get company by phone number
        companyBotSettings = CompanyBotSettings.objects.get(phone_number=phone_number, is_active=True)
        company = companyBotSettings.company
    except Company.DoesNotExist:
        return {
            'company_name': "no company configures",
            'is_office_hours': False,  # Company not found = off hours
            'is_holiday': False,
            'holiday_message': 'No holiday today.',
            'timezone_offset': 0,
            'transfer_instructions': "Company not found",
            'advisor_data': "No advisor data"
        }

    # Get company timezone
    company_timezone = pytz.timezone(company.timezone)

    # Get current time in company timezone
    current_time_utc = timezone.now()
    current_time_company = current_time_utc.astimezone(company_timezone)

    logger.info("================================= COMPANY INFO ===============================")
    logger.info(f"Current time UTC: {current_time_utc}")
    logger.info(f"Current company time: {current_time_company}")
    logger.info(f"Company timezone: {company_timezone}")

    # Calculate timezone offset
    timezone_offset = current_time_company.utcoffset().total_seconds() / 3600

    # Get current day name
    current_day = current_time_company.strftime('%A').lower()

    # Check if today is a holiday
    is_holiday = False
    today = current_time_company.date()

    logger.info(f"Today date in timezone: {today}")

    holiday = Holiday.objects.filter(
        company=company,
        bots=companyBotSettings,
        start_date__lte=current_time_company,
        end_date__gte=current_time_company,
        is_active=True
    ).first()

    logger.info(f"Holiday : {holiday}")


    if holiday:
        is_holiday = True

    # Get office hours for current day
    try:
        office_hours = OfficeHours.objects.get(
            company=company,
            day=current_day
        )

        logger.info(f"Office hours: {office_hours}")
        # Check if office is open today
        if not office_hours.is_open:
            is_office_hours = False
        else:
            # Check if current time is within working hours
            current_time_only = current_time_company.time()
            is_within_hours = office_hours.start_time <= current_time_only <= office_hours.end_time
            logger.info(f"company id: {company.id}")
            logger.info(f"company name: {company.name}")
            logger.info(f"current day: {current_day}")
            logger.info(f"office_hours.start_time: {office_hours.start_time}")
            logger.info(f"office_hours.end_time: {office_hours.end_time}")
            logger.info(f"current_time_only: {current_time_only}")
            logger.info(f"is_within_hours: {is_within_hours}")

            # Check weather company working hours are ended or not.
            # is_office_hours will be true if the call will be during office hours and there will be no holidays.
            is_office_hours = not is_holiday and is_within_hours
    except OfficeHours.DoesNotExist:
        # No office hours set for this day
        is_office_hours = False

    # Build transfer instructions
    transfer_instructions = ""

    # Get company users with phone numbers
    company_users = User.objects.filter(
        companies=company,
        is_active=True
    ).select_related('profile')

    # First statement: Loop through users with phone numbers
    user_instructions = []
    for user in company_users:
        if hasattr(user, 'profile') and user.profile.phone_number:
            advisor_name = f"{user.profile.first_name} {user.profile.last_name}".strip()
            if not advisor_name:
                advisor_name = user.username
            advisor_phone = user.profile.phone_number
            user_instructions.append(
                f"If the user wants to reach {advisor_name} then transfer to {advisor_phone};"
            )

    # Add user instructions to transfer_instructions
    if user_instructions:
        transfer_instructions += " ".join(user_instructions) + " "

    # Second statement: BDC number
    if company.bdc_number:
        transfer_instructions += (
            f"If the user wants to schedule a meeting then transfer the call to BDC {company.bdc_number} "
            f"and say 'I'll transfer you to our BDC'; "
        )

    # Third statement: Default message
    transfer_instructions += (
        "If the user asks for an advisor who is not in the list, say 'I'm sorry, we don't have an advisor by that name at our company. Would you like to connect with someone else?'"
    )

    advisor_list = []
    for user in company_users:
        if hasattr(user, 'profile') and user.profile.phone_number:
            advisor_name = f"{user.profile.first_name} {user.profile.last_name}".strip()
            advisor_list.append(f"{advisor_name} have id {user.id}")
    
    advisor_data = ", ".join(advisor_list)
    

    return {
        'company_name': company.name,
        'is_office_hours': is_office_hours,
        'holiday_message': holiday.message if holiday else 'No holiday today.',
        'is_holiday': is_holiday,
        'timezone_offset': int(timezone_offset),
        'transfer_instructions': transfer_instructions if is_office_hours and not is_holiday else "Cannot transfer calls in off hours or holidays. Please record a message for the advisor instead.",
        'advisor_data': advisor_data
    }


def log_company_change(company, user, action, model_name, object_id, details):
    """Helper function to log company changes"""
    return CompanyHistory.objects.create(
        company=company,
        updated_by=user,
        action=action,
        model_name=model_name,
        object_id=object_id,
        details=details
    )


def validate_holidays(holidays):
    """Validate holidays data"""
    invalid_holidays = holidays.filter(
        models.Q(start_date__isnull=True) | models.Q(end_date__isnull=True)
    )
    if invalid_holidays.exists():
        return 'Invalid holidays: some holidays are missing start/end date'

    # Check for invalid date ranges
    for holiday in holidays:
        if holiday.start_date and holiday.end_date and holiday.start_date > holiday.end_date:
            return f'Invalid holiday "{holiday.name}": start date must be before end date'

    return None


def validate_office_hours(office_hours):
    """Validate office hours data"""
    invalid_hours = office_hours.filter(
        is_open=True
    ).filter(
        models.Q(start_time__isnull=True) | models.Q(end_time__isnull=True)
    )
    if invalid_hours.exists():
        return 'Invalid office hours: some open days are missing start/end time'
    return None


def validate_sales_timings(sales_timings):
    """Validate sales timings data"""
    invalid_timings = sales_timings.filter(
        models.Q(start_time__isnull=True) | models.Q(end_time__isnull=True)
    )
    if invalid_timings.exists():
        return 'Invalid sales timings: some timings are missing start/end time'
    return None


def apply_to_all_companies_bulk(request, model_class, model_name, validation_func=None):
    """
    Generic utility to apply current company's data to all user's companies

    Args:
        request: Django request object
        model_class: The model class (OfficeHours, SalesTiming, etc.)
        model_name: String name for logging (e.g., 'OfficeHours', 'SalesTiming')
        validation_func: Optional custom validation function

    Returns:
        Response object
    """
    user = request.user

    if not user.active_company:
        return Response({'error': 'No active company'}, status=status.HTTP_400_BAD_REQUEST)

    # Require explicit confirmation
    if not request.data.get('confirm', False):
        return Response({'error': 'You must confirm to apply to all companies'}, status=status.HTTP_400_BAD_REQUEST)

    # Other companies
    user_companies = user.companies.filter(is_active=True).exclude(id=user.active_company.id)
    if not user_companies.exists():
        return Response({'error': 'No other companies found'}, status=status.HTTP_400_BAD_REQUEST)

    # Permission filtering
    allowed_companies = []
    denied = []
    for company in user_companies:
        if user.has_company_permission('edit_company', company):
            allowed_companies.append(company)
        else:
            denied.append(company.name)

    if not allowed_companies:
        return Response({
            'error': 'No permission to edit any company',
            'denied_companies': denied,
        }, status=status.HTTP_403_FORBIDDEN)

    # Get source data
    current_data = model_class.objects.filter(company=user.active_company)
    if not current_data.exists():
        return Response({'error': f'No {model_name.lower()} found in current company'},
                        status=status.HTTP_400_BAD_REQUEST)

    # Custom validation if provided
    if validation_func:
        validation_error = validation_func(current_data)
        if validation_error:
            return Response({'error': validation_error}, status=status.HTTP_400_BAD_REQUEST)

    try:
        with transaction.atomic():
            # 1. Delete old data for allowed companies
            model_class.objects.filter(company__in=allowed_companies).delete()

            # 2. Prepare bulk create list
            new_records = []
            for company in allowed_companies:
                for item in current_data:
                    # Create new record with company
                    new_record = model_class(
                        company=company,
                        **{field.name: getattr(item, field.name) for field in model_class._meta.fields
                           if field.name != 'id' and field.name != 'company' and not field.auto_created}
                    )
                    new_records.append(new_record)

            # Create records
            created_records = model_class.objects.bulk_create(new_records, batch_size=500)

            # 3. History logs (one per company)
            history_logs = []
            count = current_data.count()
            for company in allowed_companies:
                history_logs.append(CompanyHistory(
                    company=company,
                    updated_by=user,
                    action='bulk_replace',
                    model_name=model_name,
                    object_id=None,  # Null for bulk operations
                    details=f"Replaced {count} {model_name.lower()} from {user.active_company.name} → {company.name}"
                ))

            CompanyHistory.objects.bulk_create(history_logs, batch_size=100)

        # Success response
        response_data = {
            'message': f'{model_name} applied to {len(allowed_companies)} companies',
            'applied_count': len(allowed_companies),
            'total_companies': user_companies.count(),
            'skipped_companies': denied if denied else None,
            'source_company': user.active_company.name,
            'items_count': current_data.count()
        }

        return Response(response_data, status=status.HTTP_200_OK if not denied else status.HTTP_206_PARTIAL_CONTENT)

    except IntegrityError as e:
        error_msg = str(e)
        if 'UNIQUE constraint failed' in error_msg:
            return Response({'error': f'Duplicate {model_name.lower()} found. Please try again.'},
                            status=status.HTTP_400_BAD_REQUEST)
        elif 'FOREIGN KEY constraint failed' in error_msg:
            return Response({'error': 'Invalid company reference'}, status=status.HTTP_400_BAD_REQUEST)
        else:
            return Response({'error': f'Data constraint violation: {error_msg}'}, status=status.HTTP_400_BAD_REQUEST)
    except PermissionError:
        return Response({'error': 'Permission denied'}, status=status.HTTP_403_FORBIDDEN)
    except Exception as e:
        return Response({'error': f'Operation failed: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
