from rest_framework import viewsets, mixins, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.views import APIView
from django.shortcuts import get_object_or_404
from .models import Site, SiteConfig, Page, Domain
from .serializers import (
    SiteSerializer, SiteConfigSerializer, PageSerializer, DomainSerializer,
    PublicPageSerializer, PublicServiceSerializer, PublicSiteConfigSerializer
)
from smoothschedule.identity.core.models import Tenant
from smoothschedule.scheduling.schedule.models import Service
from smoothschedule.billing.services.entitlements import EntitlementService

class SiteViewSet(viewsets.GenericViewSet, mixins.RetrieveModelMixin, mixins.UpdateModelMixin):
    serializer_class = SiteSerializer
    permission_classes = [IsAuthenticated]

    def get_object(self):
        return get_object_or_404(Site, tenant=self.request.tenant)

    @action(detail=False, methods=['get'])
    def me(self, request):
        try:
            site = Site.objects.get(tenant=request.tenant)
        except Site.DoesNotExist:
            return Response({"detail": "Site not found"}, status=404)
        serializer = self.get_serializer(site)
        return Response(serializer.data)

    @action(detail=False, methods=['get', 'post'], url_path='me/pages')
    def me_pages(self, request):
        """Get or create pages for the current tenant's site."""
        tenant = request.tenant

        # Handle 'public' schema case (central API accessed via api.lvh.me)
        if tenant.schema_name == 'public':
            # Try to find tenant from header
            subdomain = request.headers.get('x-business-subdomain')
            if subdomain:
                try:
                    tenant = Tenant.objects.get(schema_name=subdomain)
                except Tenant.DoesNotExist:
                    return Response({"error": "Tenant not found"}, status=404)
            else:
                return Response({"error": "Business subdomain required"}, status=400)

        try:
            site = Site.objects.get(tenant=tenant)
        except Site.DoesNotExist:
            return Response({"detail": "Site not found"}, status=404)

        if request.method == 'GET':
            pages = Page.objects.filter(site=site)
            serializer = PageSerializer(pages, many=True)
            return Response(serializer.data)

        elif request.method == 'POST':
            # Check max_public_pages from billing system
            max_pages = EntitlementService.get_limit(tenant, 'max_public_pages')
            # None means unlimited, 0 means no access
            can_customize = max_pages is None or max_pages > 0

            if not can_customize:
                return Response({
                    "error": "Your plan does not include site customization. Please upgrade to a paid plan to create and edit pages."
                }, status=status.HTTP_403_FORBIDDEN)

            # Check page limit (None = unlimited)
            current_page_count = Page.objects.filter(site=site).count()

            if max_pages is not None and current_page_count >= max_pages:
                return Response({
                    "error": f"Page limit reached. Your plan allows {max_pages} page(s). Please upgrade to create more pages."
                }, status=status.HTTP_403_FORBIDDEN)

            serializer = PageSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save(site=site)
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class PageViewSet(viewsets.ModelViewSet):
    serializer_class = PageSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        return Page.objects.filter(site__tenant=self.request.tenant)

    def _check_site_permission(self, tenant):
        """Check if tenant can edit site based on max_public_pages entitlement."""
        max_pages = EntitlementService.get_limit(tenant, 'max_public_pages')
        # None means unlimited, 0 means no access
        return max_pages is None or max_pages > 0

    def update(self, request, *args, **kwargs):
        # Check max_public_pages from billing system
        if not self._check_site_permission(request.tenant):
            return Response({
                "error": "Your plan does not include site customization. Please upgrade to a paid plan to edit pages."
            }, status=status.HTTP_403_FORBIDDEN)
        return super().update(request, *args, **kwargs)

    def partial_update(self, request, *args, **kwargs):
        # Check max_public_pages from billing system
        if not self._check_site_permission(request.tenant):
            return Response({
                "error": "Your plan does not include site customization. Please upgrade to a paid plan to edit pages."
            }, status=status.HTTP_403_FORBIDDEN)
        return super().partial_update(request, *args, **kwargs)

    def destroy(self, request, *args, **kwargs):
        # Check max_public_pages from billing system
        if not self._check_site_permission(request.tenant):
            return Response({
                "error": "Your plan does not include site customization. Please upgrade to a paid plan to delete pages."
            }, status=status.HTTP_403_FORBIDDEN)
        return super().destroy(request, *args, **kwargs)

class DomainViewSet(viewsets.ModelViewSet):
    serializer_class = DomainSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        return Domain.objects.filter(site__tenant=self.request.tenant)

    def perform_create(self, serializer):
        site = get_object_or_404(Site, tenant=self.request.tenant)
        serializer.save(site=site)

    @action(detail=True, methods=['post'], url_path='set-primary')
    def set_primary(self, request, pk=None):
        domain = self.get_object()
        domain.is_primary = True
        domain.save()
        # Unset others
        site = domain.site
        Domain.objects.filter(site=site).exclude(id=domain.id).update(is_primary=False)
        return Response({'status': 'primary domain set'})

    @action(detail=True, methods=['post'])
    def verify(self, request, pk=None):
        domain = self.get_object()
        domain.is_verified = True
        domain.save()
        # TODO: Sync to core.Domain so django-tenants routes it
        return Response({'status': 'verified'})


class SiteConfigViewSet(viewsets.GenericViewSet, mixins.RetrieveModelMixin, mixins.UpdateModelMixin):
    """ViewSet for managing site configuration (theme, header, footer)."""
    serializer_class = SiteConfigSerializer
    permission_classes = [IsAuthenticated]

    def get_object(self):
        return get_object_or_404(SiteConfig, site__tenant=self.request.tenant)

    @action(detail=False, methods=['get', 'patch'])
    def me(self, request):
        """Get or update the current tenant's site config."""
        config = self.get_object()

        if request.method == 'GET':
            serializer = self.get_serializer(config)
            return Response(serializer.data)

        elif request.method == 'PATCH':
            # Check permission
            max_pages = EntitlementService.get_limit(request.tenant, 'max_public_pages')
            can_customize = max_pages is None or max_pages > 0

            if not can_customize:
                return Response({
                    "error": "Your plan does not include site customization."
                }, status=status.HTTP_403_FORBIDDEN)

            serializer = self.get_serializer(config, data=request.data, partial=True)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class PublicPageView(APIView):
    permission_classes = [AllowAny]

    def get(self, request):
        tenant = request.tenant
        
        # Handle 'public' schema case (central API)
        if tenant.schema_name == 'public':
            # Try to find tenant from header
            subdomain = request.headers.get('x-business-subdomain')
            if subdomain:
                try:
                    tenant = Tenant.objects.get(schema_name=subdomain)
                except Tenant.DoesNotExist:
                    return Response({"error": "Tenant not found"}, status=404)
            # Else we are just public (platform site?)
        
        try:
            site = Site.objects.get(tenant=tenant)
            if not site.is_enabled:
                return Response({"error": "Site disabled"}, status=404)
            
            page = Page.objects.get(site=site, is_home=True)
            serializer = PublicPageSerializer(page)
            return Response(serializer.data)
        except (Site.DoesNotExist, Page.DoesNotExist):
            return Response({"error": "Page not found"}, status=404)

class PublicServiceViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = PublicServiceSerializer
    permission_classes = [AllowAny]

    def get_queryset(self):
        if self.request.tenant.schema_name == 'public':
             return Service.objects.none()
        return Service.objects.filter(is_active=True)

class PublicAvailabilityView(APIView):
    """
    Return available time slots for a service on a given date.

    Query parameters:
    - service_id: ID of the service to check availability for
    - date: Date to check availability (YYYY-MM-DD format)

    Returns a list of time slots with availability status.
    """
    permission_classes = [AllowAny]

    def get(self, request):
        from datetime import datetime, timedelta, time
        from django.utils import timezone
        from smoothschedule.scheduling.schedule.models import Resource, TimeBlock
        from smoothschedule.scheduling.schedule.services import AvailabilityService

        if request.tenant.schema_name == 'public':
            return Response({"error": "Invalid tenant"}, status=400)

        service_id = request.query_params.get('service_id')
        date_str = request.query_params.get('date')

        if not service_id or not date_str:
            return Response({"error": "service_id and date parameters are required"}, status=400)

        try:
            service = Service.objects.get(id=service_id, is_active=True)
        except Service.DoesNotExist:
            return Response({"error": "Service not found"}, status=404)

        try:
            date = datetime.strptime(date_str, '%Y-%m-%d').date()
        except ValueError:
            return Response({"error": "Invalid date format. Use YYYY-MM-DD"}, status=400)

        # Get business hours for this date
        business_hours = self._get_business_hours_for_date(date)

        # If business is closed on this day, return empty slots with closed flag
        if not business_hours:
            return Response({
                "date": date_str,
                "service_id": service_id,
                "is_open": False,
                "slots": []
            })

        # Get resources that can perform this service
        if service.all_resources:
            resources = Resource.objects.filter(type__in=['STAFF', 'ROOM'])
        else:
            resource_ids = service.resource_ids or []
            resources = Resource.objects.filter(id__in=resource_ids)

        if not resources.exists():
            resources = Resource.objects.filter(type='STAFF')[:1]

        # Generate time slots based on business hours
        all_slots = []
        duration = timedelta(minutes=service.duration)

        start_hour, start_min = business_hours['start']
        end_hour, end_min = business_hours['end']

        # Get business timezone from tenant
        import zoneinfo
        business_tz_name = getattr(request.tenant, 'timezone', 'America/New_York')
        try:
            business_tz = zoneinfo.ZoneInfo(business_tz_name)
        except Exception:
            business_tz = zoneinfo.ZoneInfo('America/New_York')

        # Create slot times in business timezone, then convert to UTC for comparison
        naive_start = datetime.combine(date, time(start_hour, start_min))
        naive_end = datetime.combine(date, time(end_hour, end_min))

        # Make aware in business timezone
        current_time = naive_start.replace(tzinfo=business_tz)
        end_of_day = naive_end.replace(tzinfo=business_tz)

        # Get current time + buffer (30 min) to filter out past/imminent slots
        now = timezone.now()
        min_booking_time = now + timedelta(minutes=30)

        while current_time + duration <= end_of_day:
            # Skip time slots that have already passed (or are too close)
            if current_time < min_booking_time:
                current_time += timedelta(minutes=30)
                continue
            slot_end = current_time + duration
            slot_available = False

            # Check if at least one resource is available for this slot
            # Pass service for prep_time/takedown_time buffer calculations
            for resource in resources:
                is_available, _, _ = AvailabilityService.check_availability(
                    resource,
                    current_time,
                    slot_end,
                    booking_context='customer_booking',
                    service=service
                )
                if is_available:
                    slot_available = True
                    break

            all_slots.append({
                "time": current_time.isoformat(),
                "display": current_time.strftime("%-I:%M %p"),
                "available": slot_available
            })

            # Move to next 30-minute slot
            current_time += timedelta(minutes=30)

        return Response({
            "date": date_str,
            "service_id": service_id,
            "is_open": True,
            "business_hours": {
                "start": f"{start_hour:02d}:{start_min:02d}",
                "end": f"{end_hour:02d}:{end_min:02d}"
            },
            "slots": all_slots,
            "business_timezone": business_tz_name,
            "timezone_display_mode": getattr(request.tenant, 'timezone_display_mode', 'business'),
        })

    def _get_business_hours_for_date(self, date):
        """
        Get business hours for a specific date.
        Returns dict with 'start' and 'end' tuples (hour, minute), or None if closed.
        """
        from smoothschedule.scheduling.schedule.models import TimeBlock

        # Get business hours blocks (purpose=BUSINESS_HOURS, resource=null for business-level)
        business_hours_blocks = TimeBlock.objects.filter(
            resource__isnull=True,
            purpose=TimeBlock.Purpose.BUSINESS_HOURS,
            is_active=True
        )

        # If no business hours blocks defined at all, default to 9-5 every day
        if not business_hours_blocks.exists():
            return {'start': (9, 0), 'end': (17, 0)}

        # Check if any business hours block applies to this date
        for block in business_hours_blocks:
            if block.blocks_date(date):
                # This business hours block applies to this date
                if block.all_day:
                    # All day business hours (unusual but supported) - default 9-5
                    return {'start': (9, 0), 'end': (17, 0)}
                elif block.start_time and block.end_time:
                    return {
                        'start': (block.start_time.hour, block.start_time.minute),
                        'end': (block.end_time.hour, block.end_time.minute)
                    }

        # Business hours are defined but none match this date - business is CLOSED
        return None


class PublicWeeklyHoursView(APIView):
    """
    Return weekly business hours for display on public pages.
    Returns the open/close times for each day of the week.
    """
    permission_classes = [AllowAny]

    def get(self, request):
        from smoothschedule.scheduling.schedule.models import TimeBlock

        if request.tenant.schema_name == 'public':
            return Response({"error": "Invalid tenant"}, status=400)

        # Day names and indices
        DAY_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']

        # Get business hours blocks (the "before" and "after" blocks)
        business_hours_blocks = TimeBlock.objects.filter(
            resource__isnull=True,
            purpose=TimeBlock.Purpose.BUSINESS_HOURS,
            is_active=True,
            recurrence_type='WEEKLY'
        )

        # Initialize all days as closed
        weekly_hours = {i: {'is_open': False, 'open': None, 'close': None} for i in range(7)}

        # Parse the blocks to determine open hours
        # Business hours are stored as "before hours" (00:00 to open) and "after hours" (close to 23:59)
        for block in business_hours_blocks:
            if block.recurrence_pattern and 'days_of_week' in block.recurrence_pattern:
                days_of_week = block.recurrence_pattern['days_of_week']

                for day_index in days_of_week:
                    if day_index < 0 or day_index > 6:
                        continue

                    weekly_hours[day_index]['is_open'] = True

                    # Before hours block: 00:00 to open time
                    if block.start_time and str(block.start_time)[:5] == '00:00':
                        weekly_hours[day_index]['open'] = str(block.end_time)[:5] if block.end_time else '09:00'

                    # After hours block: close time to 23:59
                    elif block.end_time and str(block.end_time)[:5] in ['23:59', '00:00']:
                        weekly_hours[day_index]['close'] = str(block.start_time)[:5] if block.start_time else '17:00'

        # Build response
        result = []
        for i, day_name in enumerate(DAY_NAMES):
            day_data = weekly_hours[i]
            result.append({
                'day': day_name,
                'is_open': day_data['is_open'],
                'open': day_data['open'] if day_data['is_open'] else None,
                'close': day_data['close'] if day_data['is_open'] else None,
            })

        # If no business hours are configured, return default 9-5 Mon-Fri
        has_any_hours = any(d['is_open'] for d in result)
        if not has_any_hours:
            for i, day in enumerate(result):
                if i < 5:  # Mon-Fri
                    day['is_open'] = True
                    day['open'] = '09:00'
                    day['close'] = '17:00'

        return Response({'hours': result})


class PublicBusinessHoursView(APIView):
    """
    Return business hours for a date range.
    Used by booking calendar to show which days are open/closed.

    Query parameters:
    - start_date: Start of range (YYYY-MM-DD)
    - end_date: End of range (YYYY-MM-DD)
    """
    permission_classes = [AllowAny]

    def get(self, request):
        from datetime import datetime, timedelta
        from smoothschedule.scheduling.schedule.models import TimeBlock

        if request.tenant.schema_name == 'public':
            return Response({"error": "Invalid tenant"}, status=400)

        start_str = request.query_params.get('start_date')
        end_str = request.query_params.get('end_date')

        if not start_str or not end_str:
            return Response({"error": "start_date and end_date parameters are required"}, status=400)

        try:
            start_date = datetime.strptime(start_str, '%Y-%m-%d').date()
            end_date = datetime.strptime(end_str, '%Y-%m-%d').date()
        except ValueError:
            return Response({"error": "Invalid date format. Use YYYY-MM-DD"}, status=400)

        # Limit range to 90 days to prevent abuse
        if (end_date - start_date).days > 90:
            return Response({"error": "Date range cannot exceed 90 days"}, status=400)

        # Get business hours blocks
        business_hours_blocks = TimeBlock.objects.filter(
            resource__isnull=True,
            purpose=TimeBlock.Purpose.BUSINESS_HOURS,
            is_active=True
        )

        # Build list of open dates with hours
        result = []
        current_date = start_date

        while current_date <= end_date:
            day_info = {
                "date": current_date.isoformat(),
                "is_open": False,
                "hours": None
            }

            for block in business_hours_blocks:
                if block.blocks_date(current_date):
                    day_info["is_open"] = True
                    if not block.all_day and block.start_time and block.end_time:
                        day_info["hours"] = {
                            "start": block.start_time.strftime("%H:%M"),
                            "end": block.end_time.strftime("%H:%M")
                        }
                    else:
                        day_info["hours"] = {"start": "09:00", "end": "17:00"}
                    break

            # If no business hours block found, default to open 9-5
            if not day_info["is_open"] and not business_hours_blocks.exists():
                day_info["is_open"] = True
                day_info["hours"] = {"start": "09:00", "end": "17:00"}

            result.append(day_info)
            current_date += timedelta(days=1)

        return Response({"dates": result})

class PublicBookingView(APIView):
    permission_classes = [AllowAny]
    def post(self, request):
        if request.tenant.schema_name == 'public':
            return Response({"error": "Invalid tenant"}, status=400)
        return Response({"status": "booked", "id": 123})

class PublicPaymentIntentView(APIView):
    permission_classes = [AllowAny]
    def post(self, request):
        return Response({"client_secret": "test_secret"})


class PublicBusinessInfoView(APIView):
    """Return public business info including booking page customization."""
    permission_classes = [AllowAny]

    def get(self, request):
        tenant = request.tenant

        # Handle 'public' schema case (central API)
        if tenant.schema_name == 'public':
            subdomain = request.headers.get('x-business-subdomain')
            if subdomain:
                try:
                    tenant = Tenant.objects.get(schema_name=subdomain)
                except Tenant.DoesNotExist:
                    return Response({"error": "Tenant not found"}, status=404)
            else:
                return Response({"error": "Business subdomain required"}, status=400)

        return Response({
            "name": tenant.name,
            "logo_url": tenant.logo.url if tenant.logo else None,
            "primary_color": tenant.primary_color,
            "secondary_color": tenant.secondary_color,
            "service_selection_heading": tenant.service_selection_heading or "Choose your experience",
            "service_selection_subheading": tenant.service_selection_subheading or "Select a service to begin your booking.",
        })


class PublicSiteConfigView(APIView):
    """Return public site configuration (theme, header, footer)."""
    permission_classes = [AllowAny]

    def get(self, request):
        tenant = request.tenant

        # Handle 'public' schema case (central API)
        if tenant.schema_name == 'public':
            subdomain = request.headers.get('x-business-subdomain')
            if subdomain:
                try:
                    tenant = Tenant.objects.get(schema_name=subdomain)
                except Tenant.DoesNotExist:
                    return Response({"error": "Tenant not found"}, status=404)
            else:
                return Response({"error": "Business subdomain required"}, status=400)

        try:
            site = Site.objects.get(tenant=tenant)
            config = SiteConfig.objects.get(site=site)
            serializer = PublicSiteConfigSerializer(config)
            return Response(serializer.data)
        except (Site.DoesNotExist, SiteConfig.DoesNotExist):
            # Return default config if not found
            return Response({
                "theme": {},
                "header": {},
                "footer": {},
                "merged_theme": SiteConfig.get_default_theme()
            })


class PublicContactFormView(APIView):
    """
    Public endpoint for contact form submissions.
    Creates a CUSTOMER ticket in the ticketing system.
    """
    permission_classes = [AllowAny]

    def post(self, request):
        from smoothschedule.commerce.tickets.models import Ticket

        tenant = request.tenant

        # Handle 'public' schema case (central API)
        if tenant.schema_name == 'public':
            subdomain = request.headers.get('x-business-subdomain')
            if subdomain:
                try:
                    tenant = Tenant.objects.get(schema_name=subdomain)
                except Tenant.DoesNotExist:
                    return Response({"error": "Tenant not found"}, status=404)
            else:
                return Response({"error": "Business subdomain required"}, status=400)

        # Validate required fields
        name = request.data.get('name', '').strip()
        email = request.data.get('email', '').strip()
        message = request.data.get('message', '').strip()
        subject = request.data.get('subject', '').strip() or 'Contact Form Submission'
        phone = request.data.get('phone', '').strip()

        if not name:
            return Response({"error": "Name is required"}, status=400)
        if not email:
            return Response({"error": "Email is required"}, status=400)
        if not message:
            return Response({"error": "Message is required"}, status=400)

        # Basic email validation
        import re
        if not re.match(r'^[^@]+@[^@]+\.[^@]+$', email):
            return Response({"error": "Invalid email address"}, status=400)

        # Build description with contact info
        description_parts = [message]
        if phone:
            description_parts.append(f"\n\n---\nPhone: {phone}")

        # Create the ticket
        try:
            ticket = Ticket.objects.create(
                tenant=tenant,
                ticket_type=Ticket.TicketType.CUSTOMER,
                category=Ticket.Category.GENERAL_INQUIRY,
                priority=Ticket.Priority.MEDIUM,
                status=Ticket.Status.OPEN,
                subject=subject,
                description='\n'.join(description_parts),
                external_name=name,
                external_email=email,
                creator=None,  # No authenticated user
            )

            return Response({
                "success": True,
                "message": "Thank you for your message. We'll get back to you soon!",
                "ticket_id": ticket.id
            }, status=201)

        except Exception as e:
            return Response({
                "error": "Failed to submit contact form. Please try again."
            }, status=500)