"""Modal dialog for booking a mock interview."""

import re
from datetime import datetime, timedelta, timezone

import httpx
from textual import work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Container, Horizontal
from textual.screen import ModalScreen
from textual.widgets import Button, Input, Label, Select, Static

_E164_RE = re.compile(r"^\+[1-9]\d{1,14}$")

INTERVIEW_TYPE_OPTIONS = [
    ("Behavioral", "behavioral"),
    ("Technical Coding", "technical_coding"),
    ("System Design", "system_design"),
    ("Product Sense", "product_sense"),
    ("Analytical", "analytical"),
    ("Case Study", "case_study"),
    ("Portfolio Review", "portfolio_review"),
    ("Role Play", "role_play"),
    ("Quick Chat (~10 min, invite optional)", "quick_chat"),
]

# Detect local timezone offset for display.
_LOCAL_TZ = datetime.now().astimezone().tzinfo
_LOCAL_TZ_NAME = datetime.now().astimezone().strftime("%Z")  # e.g. "EDT", "PST"


def _date_options() -> list[tuple[str, str]]:
    """Generate date options for the next 14 days in local time.

    Returns (display_label, iso_date_str) tuples.
    Display: "Wed Apr  3" / "Today" / "Tomorrow".
    Value: "2026-04-03" (local date).
    """
    now_local = datetime.now().astimezone()
    today = now_local.date()
    options: list[tuple[str, str]] = []
    for i in range(14):
        d = today + timedelta(days=i)
        if i == 0:
            label = f"Today — {d.strftime('%a %b %d')}"
        elif i == 1:
            label = f"Tomorrow — {d.strftime('%a %b %d')}"
        else:
            label = d.strftime("%a %b %d")
        options.append((label, d.isoformat()))
    return options


def _time_options(selected_date: str | None = None) -> list[tuple[str, str]]:
    """Generate 30-minute time slot options, filtering out past times for today.

    Args:
        selected_date: ISO date string (e.g. "2026-04-03"). If today,
            slots before the current time are excluded.

    Returns (display_label, HH:MM) tuples.
    Display: "9:00 AM", "9:30 AM", etc.
    Value: "09:00", "09:30", etc.
    """
    now_local = datetime.now().astimezone()
    is_today = selected_date == now_local.date().isoformat() if selected_date else False

    options: list[tuple[str, str]] = []
    for hour in range(24):
        for minute in (0, 30):
            if is_today and (hour < now_local.hour or (hour == now_local.hour and minute <= now_local.minute)):
                continue
            t = datetime(2000, 1, 1, hour, minute)
            label = t.strftime("%I:%M %p").lstrip("0")
            value = f"{hour:02d}:{minute:02d}"
            options.append((label, value))
    return options


def _default_date() -> str:
    """Today's local date, or tomorrow if no time slots remain today."""
    now_local = datetime.now().astimezone()
    if _time_options(selected_date=now_local.date().isoformat()):
        return now_local.date().isoformat()
    return (now_local.date() + timedelta(days=1)).isoformat()


def _default_time_slot() -> str:
    """Next 5-min boundary at least 5 minutes from now, as HH:MM."""
    now = datetime.now().astimezone() + timedelta(minutes=5)
    # Round up to next 5-min boundary
    minute = (now.minute + 4) // 5 * 5
    if minute >= 60:
        now = now + timedelta(hours=1)
        minute = 0
    return f"{now.hour:02d}:{minute:02d}"


class InterviewBookModal(ModalScreen[bool]):
    """Collects interview parameters and books via the API."""

    BINDINGS = [
        Binding("escape", "cancel", "Cancel", show=False),
    ]

    DEFAULT_CSS = """
    InterviewBookModal {
        align: center middle;
        background: $surface 80%;
    }

    #interview-modal-container {
        width: 64;
        height: auto;
        border: round $accent;
        padding: 1 2;
        background: $surface;
    }

    #interview-modal-title {
        text-align: center;
        padding: 0 0 1 0;
    }

    #interview-modal-error {
        color: $error;
        height: auto;
        padding: 0 0 1 0;
    }

    .iv-field-label {
        padding: 0 0 0 0;
        height: 1;
    }

    #iv-role-input,
    #iv-invite-input,
    #iv-company-input,
    #iv-link-input,
    #iv-phone-input {
        margin: 0 0 1 0;
    }

    #iv-quick-chat-hint {
        color: $text-muted;
        padding: 0 0 1 0;
        height: 1;
    }

    #iv-type-select,
    #iv-date-select {
        margin: 0 0 1 0;
    }

    #iv-time-input {
        margin: 0 0 1 0;
    }

    #iv-buttons {
        padding: 1 0 0 0;
        align: center middle;
    }

    #iv-book-btn {
        margin: 0 1 0 0;
    }
    """

    def __init__(self, *, client) -> None:
        super().__init__()
        self.client = client
        # OTP flow state — populated when send_otp succeeds.
        self._otp_id: str = ""
        self._pending_role: str = ""
        self._pending_type: str = ""
        self._pending_scheduled_at: str = ""
        self._pending_invite_code: str = ""
        self._pending_company: str = ""

    def compose(self) -> ComposeResult:
        with Container(id="interview-modal-container"):
            yield Static("[bold]Book Mock Interview[/bold]", id="interview-modal-title")
            yield Label("Role title (required)", classes="iv-field-label")
            yield Input(
                placeholder="e.g. Senior Software Engineer",
                id="iv-role-input",
            )
            yield Label("Interview type", classes="iv-field-label")
            yield Select(
                INTERVIEW_TYPE_OPTIONS,
                id="iv-type-select",
                allow_blank=False,
                value="behavioral",
            )
            yield Label(f"Date ({_LOCAL_TZ_NAME})", classes="iv-field-label")
            yield Select(
                _date_options(),
                id="iv-date-select",
                allow_blank=False,
                value=_default_date(),
            )
            yield Label(f"Time ({_LOCAL_TZ_NAME})", classes="iv-field-label")
            yield Input(
                placeholder="e.g. 14:30, 9:05",
                value=_default_time_slot(),
                id="iv-time-input",
            )
            yield Label("Invite code (required)", classes="iv-field-label", id="iv-invite-label")
            yield Input(
                placeholder="e.g. ABC123",
                id="iv-invite-input",
            )
            yield Static("~10 min casual conversation", id="iv-quick-chat-hint")
            yield Label("Company slug (optional)", classes="iv-field-label")
            yield Input(
                placeholder="e.g. google, anthropic",
                id="iv-company-input",
            )
            yield Label("Phone number (Skilark calls you)", classes="iv-field-label", id="iv-phone-label")
            yield Input(
                placeholder="+1 xxx xxx xxxx",
                id="iv-phone-input",
            )
            yield Static("", id="interview-modal-error")
            yield Label("Enter verification code", classes="iv-field-label", id="iv-otp-label")
            yield Input(placeholder="6-digit code", id="iv-otp-input", max_length=6)
            yield Button("Verify & Book", id="iv-otp-btn", variant="primary")
            with Horizontal(id="iv-buttons"):
                yield Button("Book", id="iv-book-btn", variant="primary")
                yield Button("Cancel", id="iv-cancel-btn")

    def on_mount(self) -> None:
        """Hide the quick-chat hint, phone input, and OTP widgets on initial mount."""
        self.query_one("#iv-quick-chat-hint", Static).display = False
        self.query_one("#iv-phone-label", Label).display = False
        self.query_one("#iv-phone-input", Input).display = False
        self.query_one("#iv-otp-label", Label).display = False
        self.query_one("#iv-otp-input", Input).display = False
        self.query_one("#iv-otp-btn", Button).display = False

    def on_select_changed(self, event: Select.Changed) -> None:
        """Respond to selection changes for interview type selector."""
        if event.select.id == "iv-type-select":
            is_quick_chat = event.value == "quick_chat"
            # Update invite label — required for standard, optional for quick_chat.
            label = "Invite code (optional)" if is_quick_chat else "Invite code (required)"
            self.query_one("#iv-invite-label", Label).update(label)
            self.query_one("#iv-quick-chat-hint", Static).display = is_quick_chat
            # Show phone option for quick_chat.
            self.query_one("#iv-phone-label", Label).display = is_quick_chat
            self.query_one("#iv-phone-input", Input).display = is_quick_chat

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "iv-cancel-btn":
            self.dismiss(False)
        elif event.button.id == "iv-book-btn":
            self._submit()
        elif event.button.id == "iv-otp-btn":
            self._verify_otp()

    def _submit(self) -> None:
        error_widget = self.query_one("#interview-modal-error", Static)
        error_widget.update("")

        role = self.query_one("#iv-role-input", Input).value.strip()
        if not role:
            error_widget.update("Role title is required.")
            return

        interview_type = self.query_one("#iv-type-select", Select).value

        date_str = self.query_one("#iv-date-select", Select).value
        time_str = self.query_one("#iv-time-input", Input).value.strip()

        # Validate HH:MM format
        import re
        if not re.match(r"^\d{1,2}:\d{2}$", time_str):
            error_widget.update("Time must be HH:MM (e.g. 14:30, 9:05).")
            return
        hour, minute = int(time_str.split(":")[0]), int(time_str.split(":")[1])
        if hour > 23 or minute > 59:
            error_widget.update("Invalid time — hour 0-23, minute 0-59.")
            return
        time_str = f"{hour:02d}:{minute:02d}"

        # Combine date + time in local timezone, then convert to UTC.
        local_dt = datetime.fromisoformat(f"{date_str}T{time_str}:00").replace(
            tzinfo=_LOCAL_TZ
        )
        if local_dt < datetime.now().astimezone():
            error_widget.update("Scheduled time must be in the future.")
            return
        utc_dt = local_dt.astimezone(timezone.utc)
        scheduled_at = utc_dt.strftime("%Y-%m-%dT%H:%M:%SZ")

        invite_code = self.query_one("#iv-invite-input", Input).value.strip().upper()
        if not invite_code and interview_type != "quick_chat":
            error_widget.update("Invite code is required.")
            return

        company = self.query_one("#iv-company-input", Input).value.strip()
        phone = self.query_one("#iv-phone-input", Input).value.strip().replace(" ", "")

        if phone and not _E164_RE.match(phone):
            error_widget.update("Phone must be E.164 format (e.g. +12125551234).")
            return

        self._do_book(role, interview_type, scheduled_at, invite_code, company, phone)

    @work(thread=True, exit_on_error=False)
    def _do_book(
        self,
        role: str,
        interview_type: str,
        scheduled_at: str,
        invite_code: str,
        company: str,
        phone: str,
    ) -> None:
        # Phone quick_chat uses the OTP verification flow rather than
        # direct booking: send an OTP first, then let the user enter the
        # code before the interview is actually created.
        if phone and interview_type == "quick_chat":
            try:
                result = self.client.send_otp(phone)
            except httpx.HTTPStatusError as exc:
                try:
                    msg = exc.response.json().get("error", "Failed to send verification code.")
                except Exception:
                    msg = "Failed to send verification code."
                self.app.call_from_thread(
                    self.query_one("#interview-modal-error", Static).update, msg
                )
                return
            except Exception:
                self.app.call_from_thread(
                    self.query_one("#interview-modal-error", Static).update,
                    "Could not send verification code. Please try again.",
                )
                return
            # Stash pending booking state so _verify_otp can use it.
            self._otp_id = result.get("otp_id", "")
            self._pending_role = role
            self._pending_type = interview_type
            self._pending_scheduled_at = scheduled_at
            self._pending_invite_code = invite_code
            self._pending_company = company
            self.app.call_from_thread(self._show_otp_view)
            return

        try:
            self.client.create_interview(
                role_title=role,
                interview_type=interview_type,
                scheduled_at=scheduled_at,
                invite_code=invite_code,
                company_slug=company,
                phone_number=phone,
            )
        except httpx.HTTPStatusError as exc:
            status = exc.response.status_code
            if status == 403:
                msg = "Invalid, expired, or already used invite code."
            elif status == 409:
                body = exc.response.text
                if "capacity" in body:
                    msg = "Time slot is full — try a different time."
                elif "pending" in body:
                    msg = "You have 3 pending interviews — complete or cancel one first."
                else:
                    msg = "Booking conflict. Try again."
            elif status == 400:
                try:
                    detail = exc.response.json().get("error", "")
                    msg = f"Invalid request: {detail}" if detail else "Invalid request."
                except Exception:
                    msg = "Invalid request."
            else:
                msg = f"Failed to book interview (HTTP {status})."
            self.app.call_from_thread(
                self.query_one("#interview-modal-error", Static).update, msg
            )
            return
        except Exception:
            self.app.call_from_thread(
                self.query_one("#interview-modal-error", Static).update,
                "Something went wrong. Please try again.",
            )
            return
        self.app.call_from_thread(self.dismiss, True)

    def _show_otp_view(self) -> None:
        """Switch the modal into OTP entry mode.

        Hides the Book button and shows the OTP input widgets.  Called on
        the main thread via call_from_thread after send_otp succeeds.
        """
        self.query_one("#iv-book-btn", Button).display = False
        self.query_one("#iv-otp-label", Label).display = True
        self.query_one("#iv-otp-input", Input).display = True
        self.query_one("#iv-otp-btn", Button).display = True
        self.query_one("#interview-modal-error", Static).update(
            "Verification code sent to your phone."
        )

    @work(thread=True, exit_on_error=False)
    def _verify_otp(self) -> None:
        """Verify the OTP entered by the user and create the interview."""
        code = self.query_one("#iv-otp-input", Input).value.strip()
        if not code:
            self.app.call_from_thread(
                self.query_one("#interview-modal-error", Static).update,
                "Please enter the verification code.",
            )
            return
        try:
            self.client.verify_otp_and_book(
                otp_id=self._otp_id,
                code=code,
                role_title=self._pending_role,
                interview_type=self._pending_type,
                scheduled_at=self._pending_scheduled_at,
                invite_code=self._pending_invite_code,
                company_slug=self._pending_company,
            )
        except httpx.HTTPStatusError as exc:
            try:
                msg = exc.response.json().get("error", "Verification failed.")
            except Exception:
                msg = "Verification failed."
            self.app.call_from_thread(
                self.query_one("#interview-modal-error", Static).update, msg
            )
            return
        except Exception:
            self.app.call_from_thread(
                self.query_one("#interview-modal-error", Static).update,
                "Something went wrong. Please try again.",
            )
            return
        self.app.call_from_thread(self.dismiss, True)

    def action_cancel(self) -> None:
        self.dismiss(False)
