"""Forms / surveys / appointments list specifications — Phase 11.6."""
from __future__ import annotations

from dataclasses import dataclass
from uuid import UUID

from sqlalchemy import Select, asc, desc, or_
from sqlalchemy.sql import ColumnElement

from app.models.forms import (
    AppointmentBookingRef,
    ExperienceAppointmentPage,
    ExperienceForm,
    ExperienceSurvey,
    FormSubmission,
    SurveyResponse,
)
from app.models.types import (
    AppointmentBookingStatus,
    AppointmentPageStatus,
    FormStatus,
    FormSubmissionStatus,
    SurveyResponseStatus,
    SurveyStatus,
)

FORM_SORT_FIELDS = frozenset({"created_at", "updated_at", "code", "name", "status"})
SUBMISSION_SORT_FIELDS = frozenset({"created_at", "updated_at", "submitted_at", "status"})
SURVEY_SORT_FIELDS = frozenset({"created_at", "updated_at", "code", "name", "status"})
RESPONSE_SORT_FIELDS = frozenset({"created_at", "updated_at", "submitted_at", "status"})
APPOINTMENT_PAGE_SORT_FIELDS = frozenset(
    {"created_at", "updated_at", "code", "name", "status"}
)
BOOKING_SORT_FIELDS = frozenset(
    {"created_at", "updated_at", "slot_start", "status", "external_booking_ref"}
)


@dataclass(frozen=True, slots=True)
class FormListSpec:
    workspace_id: UUID | None = None
    site_id: UUID | None = None
    page_id: UUID | None = None
    status: FormStatus | str | None = None
    q: str | None = None
    sort_by: str = "created_at"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.workspace_id is not None:
            clauses.append(ExperienceForm.workspace_id == self.workspace_id)
        if self.site_id is not None:
            clauses.append(ExperienceForm.site_id == self.site_id)
        if self.page_id is not None:
            clauses.append(ExperienceForm.page_id == self.page_id)
        if self.status is not None:
            status = (
                self.status
                if isinstance(self.status, FormStatus)
                else FormStatus(str(self.status))
            )
            clauses.append(ExperienceForm.status == status)
        if self.q:
            term = f"%{self.q.strip()}%"
            clauses.append(
                or_(
                    ExperienceForm.code.ilike(term),
                    ExperienceForm.name.ilike(term),
                    ExperienceForm.description.ilike(term),
                )
            )
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = self.sort_by if self.sort_by in FORM_SORT_FIELDS else "created_at"
        column = getattr(ExperienceForm, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)


@dataclass(frozen=True, slots=True)
class FormSubmissionListSpec:
    workspace_id: UUID | None = None
    form_id: UUID | None = None
    status: FormSubmissionStatus | str | None = None
    q: str | None = None
    sort_by: str = "created_at"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.workspace_id is not None:
            clauses.append(FormSubmission.workspace_id == self.workspace_id)
        if self.form_id is not None:
            clauses.append(FormSubmission.form_id == self.form_id)
        if self.status is not None:
            status = (
                self.status
                if isinstance(self.status, FormSubmissionStatus)
                else FormSubmissionStatus(str(self.status))
            )
            clauses.append(FormSubmission.status == status)
        if self.q:
            term = f"%{self.q.strip()}%"
            clauses.append(
                or_(
                    FormSubmission.crm_contact_ref.ilike(term),
                    FormSubmission.external_ref.ilike(term),
                    FormSubmission.communication_ref.ilike(term),
                )
            )
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = (
            self.sort_by if self.sort_by in SUBMISSION_SORT_FIELDS else "created_at"
        )
        column = getattr(FormSubmission, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)


@dataclass(frozen=True, slots=True)
class SurveyListSpec:
    workspace_id: UUID | None = None
    site_id: UUID | None = None
    page_id: UUID | None = None
    status: SurveyStatus | str | None = None
    q: str | None = None
    sort_by: str = "created_at"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.workspace_id is not None:
            clauses.append(ExperienceSurvey.workspace_id == self.workspace_id)
        if self.site_id is not None:
            clauses.append(ExperienceSurvey.site_id == self.site_id)
        if self.page_id is not None:
            clauses.append(ExperienceSurvey.page_id == self.page_id)
        if self.status is not None:
            status = (
                self.status
                if isinstance(self.status, SurveyStatus)
                else SurveyStatus(str(self.status))
            )
            clauses.append(ExperienceSurvey.status == status)
        if self.q:
            term = f"%{self.q.strip()}%"
            clauses.append(
                or_(
                    ExperienceSurvey.code.ilike(term),
                    ExperienceSurvey.name.ilike(term),
                    ExperienceSurvey.description.ilike(term),
                )
            )
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = self.sort_by if self.sort_by in SURVEY_SORT_FIELDS else "created_at"
        column = getattr(ExperienceSurvey, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)


@dataclass(frozen=True, slots=True)
class SurveyResponseListSpec:
    workspace_id: UUID | None = None
    survey_id: UUID | None = None
    status: SurveyResponseStatus | str | None = None
    q: str | None = None
    sort_by: str = "created_at"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.workspace_id is not None:
            clauses.append(SurveyResponse.workspace_id == self.workspace_id)
        if self.survey_id is not None:
            clauses.append(SurveyResponse.survey_id == self.survey_id)
        if self.status is not None:
            status = (
                self.status
                if isinstance(self.status, SurveyResponseStatus)
                else SurveyResponseStatus(str(self.status))
            )
            clauses.append(SurveyResponse.status == status)
        if self.q:
            term = f"%{self.q.strip()}%"
            clauses.append(
                or_(
                    SurveyResponse.crm_contact_ref.ilike(term),
                    SurveyResponse.external_ref.ilike(term),
                )
            )
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = (
            self.sort_by if self.sort_by in RESPONSE_SORT_FIELDS else "created_at"
        )
        column = getattr(SurveyResponse, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)


@dataclass(frozen=True, slots=True)
class AppointmentPageListSpec:
    workspace_id: UUID | None = None
    site_id: UUID | None = None
    page_id: UUID | None = None
    status: AppointmentPageStatus | str | None = None
    q: str | None = None
    sort_by: str = "created_at"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.workspace_id is not None:
            clauses.append(ExperienceAppointmentPage.workspace_id == self.workspace_id)
        if self.site_id is not None:
            clauses.append(ExperienceAppointmentPage.site_id == self.site_id)
        if self.page_id is not None:
            clauses.append(ExperienceAppointmentPage.page_id == self.page_id)
        if self.status is not None:
            status = (
                self.status
                if isinstance(self.status, AppointmentPageStatus)
                else AppointmentPageStatus(str(self.status))
            )
            clauses.append(ExperienceAppointmentPage.status == status)
        if self.q:
            term = f"%{self.q.strip()}%"
            clauses.append(
                or_(
                    ExperienceAppointmentPage.code.ilike(term),
                    ExperienceAppointmentPage.name.ilike(term),
                    ExperienceAppointmentPage.booking_provider_ref.ilike(term),
                    ExperienceAppointmentPage.service_ref.ilike(term),
                )
            )
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = (
            self.sort_by
            if self.sort_by in APPOINTMENT_PAGE_SORT_FIELDS
            else "created_at"
        )
        column = getattr(ExperienceAppointmentPage, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)


@dataclass(frozen=True, slots=True)
class AppointmentBookingListSpec:
    workspace_id: UUID | None = None
    appointment_page_id: UUID | None = None
    status: AppointmentBookingStatus | str | None = None
    q: str | None = None
    sort_by: str = "created_at"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.workspace_id is not None:
            clauses.append(AppointmentBookingRef.workspace_id == self.workspace_id)
        if self.appointment_page_id is not None:
            clauses.append(
                AppointmentBookingRef.appointment_page_id == self.appointment_page_id
            )
        if self.status is not None:
            status = (
                self.status
                if isinstance(self.status, AppointmentBookingStatus)
                else AppointmentBookingStatus(str(self.status))
            )
            clauses.append(AppointmentBookingRef.status == status)
        if self.q:
            term = f"%{self.q.strip()}%"
            clauses.append(
                or_(
                    AppointmentBookingRef.external_booking_ref.ilike(term),
                    AppointmentBookingRef.crm_contact_ref.ilike(term),
                )
            )
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = self.sort_by if self.sort_by in BOOKING_SORT_FIELDS else "created_at"
        column = getattr(AppointmentBookingRef, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)
