"""
PowerPath Input Types

Pydantic models for request inputs and query parameters,
matching the Zod schemas from the TypeScript SDK.
"""

from __future__ import annotations

from typing import Annotated, Any, Literal

from pydantic import BaseModel, Field, HttpUrl, model_validator

from timeback_common import NonEmptyString  # noqa: TC001

from .base import (  # noqa: TC001
    PowerPathLessonType,
    ResourceStatus,
    ScoreStatus,
    ScreeningSubject,
    TestAssignmentStatus,
    TimebackGrade,
    TimebackSubject,
    ToolProvider,
)

# ═══════════════════════════════════════════════════════════════════════════════
# ASSESSMENT INPUTS
# ═══════════════════════════════════════════════════════════════════════════════


class CreateExternalPlacementTestInput(BaseModel):
    """Input for creating an external placement test."""

    course_id: NonEmptyString = Field(alias="courseId")
    lesson_type: Literal["placement"] = Field(default="placement", alias="lessonType")
    lesson_title: NonEmptyString | None = Field(default=None, alias="lessonTitle")
    launch_url: NonEmptyString | None = Field(default=None, alias="launchUrl")
    tool_provider: ToolProvider = Field(alias="toolProvider")
    unit_title: NonEmptyString | None = Field(default=None, alias="unitTitle")
    course_component_sourced_id: NonEmptyString | None = Field(
        default=None, alias="courseComponentSourcedId"
    )
    vendor_id: NonEmptyString | None = Field(default=None, alias="vendorId")
    description: NonEmptyString | None = None
    resource_metadata: dict[str, Any] | None = Field(default=None, alias="resourceMetadata")
    grades: list[TimebackGrade]
    course_id_on_fail: NonEmptyString | None = Field(default=None, alias="courseIdOnFail")
    xp: float | None = None

    model_config = {"populate_by_name": True}


class CreateExternalTestOutInput(BaseModel):
    """Input for creating an external test-out."""

    course_id: NonEmptyString = Field(alias="courseId")
    lesson_type: Literal["test-out"] = Field(default="test-out", alias="lessonType")
    lesson_title: NonEmptyString | None = Field(default=None, alias="lessonTitle")
    launch_url: NonEmptyString | None = Field(default=None, alias="launchUrl")
    tool_provider: ToolProvider = Field(alias="toolProvider")
    unit_title: NonEmptyString | None = Field(default=None, alias="unitTitle")
    course_component_sourced_id: NonEmptyString | None = Field(
        default=None, alias="courseComponentSourcedId"
    )
    vendor_id: NonEmptyString | None = Field(default=None, alias="vendorId")
    description: NonEmptyString | None = None
    resource_metadata: dict[str, Any] | None = Field(default=None, alias="resourceMetadata")
    grades: list[TimebackGrade]
    xp: float

    model_config = {"populate_by_name": True}


class QtiTestConfig(BaseModel):
    """QTI test configuration for internal test creation."""

    url: HttpUrl
    title: NonEmptyString | None = None
    metadata: dict[str, Any] | None = None


class AssessmentBankResource(BaseModel):
    """A single resource in an assessment bank."""

    url: HttpUrl
    title: NonEmptyString | None = None
    metadata: dict[str, Any] | None = None


class AssessmentBankConfig(BaseModel):
    """Assessment bank configuration for internal test creation."""

    resources: list[AssessmentBankResource] = Field(min_length=1)


class CreateInternalTestInput(BaseModel):
    """Input for creating an internal test (QTI or assessment-bank)."""

    course_id: NonEmptyString = Field(alias="courseId")
    lesson_type: PowerPathLessonType = Field(alias="lessonType")
    test_type: Literal["qti", "assessment-bank"] = Field(alias="testType")
    lesson_title: NonEmptyString | None = Field(default=None, alias="lessonTitle")
    unit_title: NonEmptyString | None = Field(default=None, alias="unitTitle")
    course_component_sourced_id: NonEmptyString | None = Field(
        default=None, alias="courseComponentSourcedId"
    )
    resource_metadata: dict[str, Any] | None = Field(default=None, alias="resourceMetadata")
    xp: float | None = None
    grades: list[TimebackGrade] | None = None
    course_id_on_fail: NonEmptyString | None = Field(default=None, alias="courseIdOnFail")
    qti: QtiTestConfig | None = None
    assessment_bank: AssessmentBankConfig | None = Field(default=None, alias="assessmentBank")

    model_config = {"populate_by_name": True}

    @model_validator(mode="after")
    def _validate_test_type(self) -> CreateInternalTestInput:
        if self.test_type == "qti":
            if self.qti is None:
                raise ValueError("qti config is required when testType is 'qti'")
            if self.assessment_bank is not None:
                raise ValueError("assessmentBank must not be set when testType is 'qti'")
        if self.test_type == "assessment-bank":
            if self.assessment_bank is None:
                raise ValueError(
                    "assessmentBank config is required when testType is 'assessment-bank'"
                )
            if self.qti is not None:
                raise ValueError("qti must not be set when testType is 'assessment-bank'")
        return self


class CreateNewAttemptInput(BaseModel):
    """Input for creating a new attempt."""

    student: NonEmptyString
    lesson: NonEmptyString

    model_config = {"populate_by_name": True}


class FinalStudentAssessmentResponseInput(BaseModel):
    """Input for finalizing a student assessment response."""

    student: NonEmptyString
    lesson: NonEmptyString

    model_config = {"populate_by_name": True}


class GetAssessmentProgressParams(BaseModel):
    """Parameters for getting assessment progress."""

    student: NonEmptyString
    lesson: NonEmptyString
    attempt: int | None = Field(default=None, gt=0)

    model_config = {"populate_by_name": True}


class GetNextQuestionParams(BaseModel):
    """Parameters for getting the next question."""

    student: NonEmptyString
    lesson: NonEmptyString

    model_config = {"populate_by_name": True}


class GetAttemptsParams(BaseModel):
    """Parameters for getting attempts."""

    student: NonEmptyString
    lesson: NonEmptyString

    model_config = {"populate_by_name": True}


class UpdateStudentQuestionResponseInput(BaseModel):
    """Input for updating a student question response."""

    student: NonEmptyString
    question: NonEmptyString
    response: NonEmptyString | list[NonEmptyString] | None = None
    responses: dict[str, NonEmptyString | list[NonEmptyString]] | None = None
    lesson: NonEmptyString

    model_config = {"populate_by_name": True}

    @model_validator(mode="after")
    def _validate_response(self) -> UpdateStudentQuestionResponseInput:
        if self.response is None and self.responses is None:
            raise ValueError("Either 'response' or 'responses' must be provided")
        if self.response is not None and self.responses is None:
            self.responses = {"RESPONSE": self.response}
        return self


class ResetAttemptInput(BaseModel):
    """Input for resetting an attempt."""

    student: NonEmptyString
    lesson: NonEmptyString

    model_config = {"populate_by_name": True}


class TestOutParams(BaseModel):
    """Parameters for test out."""

    student: NonEmptyString
    course: NonEmptyString

    model_config = {"populate_by_name": True}


class ImportExternalTestAssignmentResultsParams(BaseModel):
    """Parameters for importing external test assignment results."""

    student: NonEmptyString
    lesson: NonEmptyString
    application_name: NonEmptyString | None = Field(default=None, alias="applicationName")

    model_config = {"populate_by_name": True}


class MakeExternalTestAssignmentInput(BaseModel):
    """Input for making an external test assignment."""

    student: NonEmptyString
    lesson: NonEmptyString
    application_name: NonEmptyString | None = Field(default=None, alias="applicationName")
    test_id: NonEmptyString | None = Field(default=None, alias="testId")
    skip_course_enrollment: bool | None = Field(default=None, alias="skipCourseEnrollment")

    model_config = {"populate_by_name": True}


# ═══════════════════════════════════════════════════════════════════════════════
# TEST ASSIGNMENTS INPUTS
# ═══════════════════════════════════════════════════════════════════════════════


class TestAssignmentsCreateInput(BaseModel):
    """Input for creating a test assignment."""

    student: NonEmptyString
    subject: TimebackSubject
    grade: TimebackGrade
    test_name: NonEmptyString | None = Field(default=None, alias="testName")

    model_config = {"populate_by_name": True}


class TestAssignmentsUpdateInput(BaseModel):
    """Input for updating a test assignment."""

    test_name: NonEmptyString = Field(alias="testName")

    model_config = {"populate_by_name": True}


class TestAssignmentItemInput(BaseModel):
    """A single item in a bulk test assignment operation."""

    student: NonEmptyString
    subject: TimebackSubject
    grade: TimebackGrade
    test_name: NonEmptyString | None = Field(default=None, alias="testName")

    model_config = {"populate_by_name": True}


class TestAssignmentsBulkInput(BaseModel):
    """Input for bulk test assignment operations."""

    items: list[TestAssignmentItemInput]


class TestAssignmentsImportInput(BaseModel):
    """Input for importing test assignments from a spreadsheet."""

    spreadsheet_url: HttpUrl = Field(alias="spreadsheetUrl")
    sheet: NonEmptyString

    model_config = {"populate_by_name": True}


class TestAssignmentsListParams(BaseModel):
    """Parameters for listing test assignments."""

    student: NonEmptyString
    status: TestAssignmentStatus | None = None
    subject: NonEmptyString | None = None
    grade: TimebackGrade | None = None
    limit: int | None = Field(default=None, gt=0, le=3000)
    offset: int | None = Field(default=None, ge=0)

    model_config = {"populate_by_name": True}


class TestAssignmentsAdminParams(BaseModel):
    """Parameters for admin listing test assignments."""

    student: NonEmptyString | None = None
    status: TestAssignmentStatus | None = None
    subject: NonEmptyString | None = None
    grade: TimebackGrade | None = None
    limit: int | None = Field(default=None, gt=0, le=3000)
    offset: int | None = Field(default=None, ge=0)

    model_config = {"populate_by_name": True}


# ═══════════════════════════════════════════════════════════════════════════════
# LESSON PLAN INPUTS
# ═══════════════════════════════════════════════════════════════════════════════


class LessonPlansCreateInput(BaseModel):
    """Input for creating a lesson plan."""

    course_id: NonEmptyString = Field(alias="courseId")
    user_id: NonEmptyString = Field(alias="userId")
    class_id: NonEmptyString | None = Field(default=None, alias="classId")

    model_config = {"populate_by_name": True}


class LessonPlanTarget(BaseModel):
    """Target for a lesson plan operation (resource or component)."""

    type: Literal["resource", "component"]
    id: NonEmptyString


# -- Payload models (internal to this module) ----------------------------------


class _SetSkippedPayload(BaseModel):
    target: LessonPlanTarget
    value: bool


class _AddCustomResourcePayload(BaseModel):
    resource_id: NonEmptyString
    parent_component_id: NonEmptyString
    skipped: bool | None = None


class _MoveItemRelativePayload(BaseModel):
    target: LessonPlanTarget
    reference_id: NonEmptyString


class _MoveItemTargetOnlyPayload(BaseModel):
    target: LessonPlanTarget


class _ChangeItemParentPayload(BaseModel):
    target: LessonPlanTarget
    new_parent_id: NonEmptyString
    position: Literal["start", "end"] | None = None


# -- Operation variant models -------------------------------------------------


class SetSkippedOperation(BaseModel):
    """set-skipped lesson plan operation."""

    type: Literal["set-skipped"]
    payload: _SetSkippedPayload


class AddCustomResourceOperation(BaseModel):
    """add-custom-resource lesson plan operation."""

    type: Literal["add-custom-resource"]
    payload: _AddCustomResourcePayload


class MoveItemBeforeOperation(BaseModel):
    """move-item-before lesson plan operation."""

    type: Literal["move-item-before"]
    payload: _MoveItemRelativePayload


class MoveItemAfterOperation(BaseModel):
    """move-item-after lesson plan operation."""

    type: Literal["move-item-after"]
    payload: _MoveItemRelativePayload


class MoveItemToStartOperation(BaseModel):
    """move-item-to-start lesson plan operation."""

    type: Literal["move-item-to-start"]
    payload: _MoveItemTargetOnlyPayload


class MoveItemToEndOperation(BaseModel):
    """move-item-to-end lesson plan operation."""

    type: Literal["move-item-to-end"]
    payload: _MoveItemTargetOnlyPayload


class ChangeItemParentOperation(BaseModel):
    """change-item-parent lesson plan operation."""

    type: Literal["change-item-parent"]
    payload: _ChangeItemParentPayload


LessonPlanOperationInput = Annotated[
    SetSkippedOperation
    | AddCustomResourceOperation
    | MoveItemBeforeOperation
    | MoveItemAfterOperation
    | MoveItemToStartOperation
    | MoveItemToEndOperation
    | ChangeItemParentOperation,
    Field(discriminator="type"),
]


class LessonPlanOperationsInput(BaseModel):
    """Input for creating lesson plan operations."""

    operation: LessonPlanOperationInput
    reason: NonEmptyString | None = None


class LearningObjectiveResult(BaseModel):
    """Learning objective result in a student item response."""

    learning_objective_id: NonEmptyString = Field(alias="learningObjectiveId")
    score: float | None = None
    text_score: NonEmptyString | None = Field(default=None, alias="textScore")

    model_config = {"populate_by_name": True}


class LearningObjectiveSet(BaseModel):
    """Set of learning objective results."""

    source: NonEmptyString
    learning_objective_results: list[LearningObjectiveResult] = Field(
        alias="learningObjectiveResults"
    )

    model_config = {"populate_by_name": True}


class StudentItemResult(BaseModel):
    """Result data for a student item response update."""

    status: ResourceStatus
    metadata: dict[str, Any] | None = None
    score: float | None = None
    text_score: NonEmptyString | None = Field(default=None, alias="textScore")
    score_date: NonEmptyString = Field(alias="scoreDate")
    score_percentile: float | None = Field(default=None, alias="scorePercentile")
    score_status: ScoreStatus = Field(alias="scoreStatus")
    comment: NonEmptyString | None = None
    learning_objective_set: list[LearningObjectiveSet] | None = Field(
        default=None, alias="learningObjectiveSet"
    )
    in_progress: NonEmptyString | None = Field(default=None, alias="inProgress")
    incomplete: NonEmptyString | None = None
    late: NonEmptyString | None = None
    missing: NonEmptyString | None = None

    model_config = {"populate_by_name": True}


class LessonPlanUpdateStudentItemResponseInput(BaseModel):
    """Input for updating a student item response."""

    student_id: NonEmptyString = Field(alias="studentId")
    component_resource_id: NonEmptyString = Field(alias="componentResourceId")
    result: StudentItemResult

    model_config = {"populate_by_name": True}


# ═══════════════════════════════════════════════════════════════════════════════
# PLACEMENT INPUTS
# ═══════════════════════════════════════════════════════════════════════════════


class PlacementQueryParams(BaseModel):
    """Parameters for placement queries."""

    student: NonEmptyString
    subject: TimebackSubject

    model_config = {"populate_by_name": True}


class PlacementResetUserPlacementInput(BaseModel):
    """Input for resetting user placement."""

    student: NonEmptyString
    subject: TimebackSubject

    model_config = {"populate_by_name": True}


# ═══════════════════════════════════════════════════════════════════════════════
# SCREENING INPUTS
# ═══════════════════════════════════════════════════════════════════════════════


class ScreeningResetSessionInput(BaseModel):
    """Input for resetting a screening session."""

    user_id: NonEmptyString = Field(alias="userId")

    model_config = {"populate_by_name": True}


class ScreeningAssignTestInput(BaseModel):
    """Input for assigning a screening test."""

    user_id: NonEmptyString = Field(alias="userId")
    subject: ScreeningSubject

    model_config = {"populate_by_name": True}


# ═══════════════════════════════════════════════════════════════════════════════
# SYLLABUS INPUTS
# ═══════════════════════════════════════════════════════════════════════════════


class SyllabusQueryParams(BaseModel):
    """Parameters for syllabus queries."""

    status: ResourceStatus | None = None

    model_config = {"populate_by_name": True}


__all__ = [
    "AddCustomResourceOperation",
    "AssessmentBankConfig",
    "AssessmentBankResource",
    "ChangeItemParentOperation",
    "CreateExternalPlacementTestInput",
    "CreateExternalTestOutInput",
    "CreateInternalTestInput",
    "CreateNewAttemptInput",
    "FinalStudentAssessmentResponseInput",
    "GetAssessmentProgressParams",
    "GetAttemptsParams",
    "GetNextQuestionParams",
    "ImportExternalTestAssignmentResultsParams",
    "LearningObjectiveResult",
    "LearningObjectiveSet",
    "LessonPlanOperationInput",
    "LessonPlanOperationsInput",
    "LessonPlanTarget",
    "LessonPlanUpdateStudentItemResponseInput",
    "LessonPlansCreateInput",
    "MakeExternalTestAssignmentInput",
    "MoveItemAfterOperation",
    "MoveItemBeforeOperation",
    "MoveItemToEndOperation",
    "MoveItemToStartOperation",
    "PlacementQueryParams",
    "PlacementResetUserPlacementInput",
    "QtiTestConfig",
    "ResetAttemptInput",
    "ScreeningAssignTestInput",
    "ScreeningResetSessionInput",
    "SetSkippedOperation",
    "StudentItemResult",
    "SyllabusQueryParams",
    "TestAssignmentItemInput",
    "TestAssignmentsAdminParams",
    "TestAssignmentsBulkInput",
    "TestAssignmentsCreateInput",
    "TestAssignmentsImportInput",
    "TestAssignmentsListParams",
    "TestAssignmentsUpdateInput",
    "TestOutParams",
    "UpdateStudentQuestionResponseInput",
]
