import logging
from typing import Dict, Any, Optional

from datetime import timedelta

from django.conf import settings
from rest_framework.exceptions import ValidationError

from apps.calls.constants import BotType
from apps.calls.services import EmailService
from apps.companies.models import CompanyBotSettings
from apps.appointments.repositories import AppointmentRepository
from apps.appointments.models import WebhookEvent
from utils.timezone_utils import (
    to_utc, dt_str_to_iso,
    tz_info_from_offset,
    smart_parse_datetime,
    dt_to_utc, InvalidAppointmentDatetime
)
from .xtime_client import book_appointment_x_time
from .appointment_notification_service import (
    AppointmentNotificationService
)

logger = logging.getLogger(__name__)
OIL_CHANGE_DURATION = 130
MINOR_SERVICE_DURATION = 45


class AppointmentService:
    """
    Service layer that handles webhook payload normalization and uses the repository.
    Services accept validated data (e.g. BookAppointmentInputSerializer output).
    """

    @staticmethod
    def _extract_common_webhook_data(webhook_payload: Dict[str, Any]) -> Dict[str, Any]:
        """
        Normalize webhook payload into predictable fields.
        """
        args = webhook_payload.get("args") or {}
        call_info = webhook_payload.get("call") or {}
        tel_identifier = (call_info.get("telephony_identifier") or {})
        retell_variables = (
                call_info.get("retell_llm_dynamic_variables") or {}
        )
        timezone_offset = retell_variables.get(
            "timezone_offset"
        ) or None
        is_office_hours = retell_variables.get(
            "is_office_hours"
        ) or None

        return {
            "args": args,
            "twilio_call_sid": tel_identifier.get("twilio_call_sid"),
            "to_number": call_info.get("to_number"),
            "from_number": call_info.get("from_number"),
            "timezone_offset" : timezone_offset, # timezone_offset = "-8"
            "is_office_hours" : is_office_hours, # is_office_hours = "True"
        }

    @staticmethod
    def _get_company_from_phone(phone_number: Optional[str]):
        """
        Return Company instance matched from CompanyBotSettings or None if not found.
        This is defensive: original code used .first().company and would AttributeError
        if missing; returning None prevents server 500s. If your business rules require
        raising on missing setting, change this accordingly.
        """
        if not phone_number:
            logger.warning("No phone number provided in webhook to lookup company.")
            return None

        cbs = CompanyBotSettings.objects.filter(phone_number=phone_number).first()
        if not cbs:
            logger.warning(
                "CompanyBotSettings not found for phone: %s",
                phone_number
            )
            return None
        return cbs.company

    @staticmethod
    def book_sales_callback(validated_payload: Dict[str, Any]):
        """
        Schedule a sales call back if person not available.
        """
        data = AppointmentService._extract_common_webhook_data(validated_payload)
        args = data["args"] or {}
        company = AppointmentService._get_company_from_phone(data.get("to_number"))

        dt_str = args.get("schedule")
        timezone_offset = data['timezone_offset']
        tz_info = tz_info_from_offset(timezone_offset)
        tz_schedule_dt = to_utc(dt_str, tz_info)

        payload = {
            "name": args.get("name"),
            "scheduled_date": tz_schedule_dt,
            "offtime": args.get("offTime"),
            "twilio_call_sid": data.get("twilio_call_sid"),
            "company": company,
            "call": None,
            "bot_type": BotType.SALES_BOT.value,
        }

        appointment = AppointmentRepository.create_or_update_by_call_sid(payload)

        return appointment

    @staticmethod
    def book_service_appointment(validated_payload: Dict[str, Any]):
        """
        Book an appointment for service bot using normalized payload.
        """
        data = AppointmentService._extract_common_webhook_data(validated_payload)
        args = data["args"] or {}
        company = AppointmentService._get_company_from_phone(data.get("to_number"))

        dt_str = (
                args.get("appointment_datetime")
                or args.get("booking_datetime")
        ) # "2024-06-14 10:30:00" or "2026-01-27T13:00:00" or "Wednesday, January 28, 2026 at 8:00am"
        # dt_object = smart_parse_datetime(dt_str)

        try:
            dt_object = smart_parse_datetime(dt_str)
        except InvalidAppointmentDatetime as exc:
            logger.warning(f"Invalid appointment datetime: {exc}")
            raise ValidationError({
                "appointment_datetime": str(exc)
            })

        timezone_offset = data['timezone_offset']
        tz_info = tz_info_from_offset(timezone_offset)
        tz_schedule_dt = dt_to_utc(dt_object, tz_info)
        offtime = 1 if str(data["is_office_hours"]).lower() == "true" else None
        customer_number = data["from_number"]
        customer_name = args.get("name") or args.get("customer_name")

        booking_type = (args.get("service") or "").strip().lower() or "oil change"

        logger.info(f"=================== Appointment Payload ===============\n: {data}")

        payload = {
            "name": customer_name,
            "appointment_phone": customer_number,
            "scheduled_date": tz_schedule_dt,
            "twilio_call_sid": data.get("twilio_call_sid"),
            "offtime" : offtime,
            "company": company,
            "call": None,
            "bot_type": BotType.SERVICE_BOT.value,
            "booking_type": booking_type,
        }

        appointment = AppointmentRepository.create_or_update_by_call_sid(payload)
        logger.info(f"=================== Appointment Info ===============\n: {appointment}")

        # logger.warning(f"customer provided phone {args.get('phone_number')}")

        try:
            AppointmentNotificationService.process(
                appointment,
                extra={
                    "make": args.get('vehicle_make'),
                    "model":  args.get('vehicle_model'),
                    "year":  args.get('vehicle_year'),
                    "customer_number":  args.get('phone_number'),
                }
            )
        except Exception as exc:
            logger.warning(
                "Failed to send appointment notifications for appointment id=%s: %s",
                appointment.id,
                exc,
                exc_info=True
            )
        return appointment

    @staticmethod
    def get_appointment_by_id(appointment_id: int):
        """
        Retrieve an appointment by its ID.
        """
        return AppointmentRepository.get_by_id(appointment_id)

    @staticmethod
    def get_week_day(booking_datetime: str) -> Optional[str]:
        """
        Return the weekday name for a given datetime.
        """
        if not booking_datetime:
            return None
        if isinstance(booking_datetime, str):
            booking_datetime = dt_str_to_iso(booking_datetime)
            if not booking_datetime:
                return None

        return booking_datetime.strftime("%A")


def x_time_appointment(
        booking_type,
        customer_name,
        customer_number,
        dt_object,
        args
):
    event = None
    try:
        appointment_time = (
            OIL_CHANGE_DURATION
            if booking_type == "oil change"
            else MINOR_SERVICE_DURATION
        )

        event = WebhookEvent.objects.create(
            source='XTime',
            event_type='Appointment',
            headers={},
            raw_payload={},
        )

        parts = customer_name.strip().split()

        if len(parts) == 1:
            first_name = parts[0]
            last_name = 'Unknown'
        else:
            first_name = parts[0]
            last_name = " ".join(parts[1:])

        end_dt = dt_object + timedelta(minutes=appointment_time)
        vehicle_model = args.get('vehicle_model')
        vehicle_make = args.get('vehicle_make')
        vehicle_year = args.get('vehicle_year')

        event.headers = {
            'customer_name': customer_name,
            'customer_number': customer_number,
            "provided_number": args.get('phone_number'),
            'parts': parts,
            'first_name': first_name,
            'last_name': last_name,
            'appointment_time': appointment_time,
            'dt_str': dt_str,
            'vehicle_model': vehicle_model,
            'vehicle_make': vehicle_make,
            'vehicle_year': vehicle_year,
        }

        event.save(update_fields=["headers"])

        if customer_number:
            customer_number = customer_number.lstrip('+')

        x_booking_response, x_date_time, opcode, service = book_appointment_x_time(
            appointment_start_dt=dt_object,
            appointment_end_dt=end_dt,
            first_name=first_name,
            last_name=last_name,
            email='m.mudassir@flutenai.com',
            phone=customer_number,
            year=int(vehicle_year),
            make=vehicle_make,
            model=vehicle_model,
            service_name=booking_type,
        )

        event.raw_payload={
            'x_booking_response': x_booking_response,
            'x_date_time': x_date_time,
            'opcode': opcode,
            'service': service,
        }
        event.save(update_fields=["raw_payload"])
        if x_booking_response.get('success') is True:
            event.mark_success()
        else:
            message = x_booking_response.get('message') or ""
            event.mark_failed(
                f"X Time Booking Success False {message}"
            )

            ctx = {
                "recipient_name": "Support Team",
                "alert_title": "X Time Booking Failed",
                "alert_icon": "🚨",
                "alert_message": "An appointment booking attempt has failed and needs review.",
                "alert_type": "Booking Error",
                "alert_description": f"X Time returned success = False with an invalid date format. {message}",

                "details": {
                    "Customer Name": customer_name,
                    "Customer Phone": customer_number,
                    "Booking Type": booking_type,
                    "Appointment Time": str(dt_object),
                    "Vehicle": f"{vehicle_year} {vehicle_make} {vehicle_model}",
                    "Company": company.name if company else "N/A",
                },

                "action_url": "https://dp2.front.fluten.xyz/",
                "action_text": "Open Admin Panel",

                "button_color": "#dc2626",
                "text_color": "#dc2626",

                "system_name": "DealerPulse",
                "current_year": 2026,
            }

            EmailService.send_email(
                subject=f"X Time Booking Failed",
                html_content="alerts/general_alert.html",
                recipient_list=settings.EMPTY_RECIPIENT_NOTIFY_EMAILS,
                key=ctx,
            )

    except Exception as exc:
        logger.warning(f"X Time Booking Exception {exc}")
        if event:
            event.mark_failed(f"X Time Booking Exception {exc}")
        pass
