package org.nstart.dep265.requestservice.dto

import com.fasterxml.jackson.annotation.JsonAnySetter
import com.fasterxml.jackson.annotation.JsonIgnore
import jakarta.validation.Valid
import jakarta.validation.constraints.AssertTrue
import jakarta.validation.constraints.DecimalMax
import jakarta.validation.constraints.DecimalMin
import jakarta.validation.constraints.Max
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Size
import java.time.LocalDateTime
import java.util.UUID

/**
 * Базовая защита OpenAPI DTO от полей вне schema.additionalProperties=false.
 */
abstract class StrictRequestApiDto {
    @JsonAnySetter
    fun rejectAdditionalProperty(name: String, value: Any?) {
        throw IllegalArgumentException("Unknown request API property: $name")
    }
}

/**
 * Тело запроса на создание заявки по OpenAPI.
 */
data class CreateRequestDto(
    /** Внешний идентификатор заявки. */
    @field:NotNull
    val id: UUID,

    /** Наименование задания. */
    @field:NotBlank
    @field:Size(max = 255)
    val name: String,

    /** Геометрия заявки в WKT. */
    @field:NotBlank
    val geometry: String,

    /** Важность заявки для расчета важности ячеек сетки. */
    @field:NotNull
    @field:DecimalMin("0")
    val importance: Double?,

    /** Начало временного окна выполнения заявки. */
    @field:NotNull
    val beginDateTime: LocalDateTime,

    /** Окончание временного окна выполнения заявки. */
    @field:NotNull
    val endDateTime: LocalDateTime,

    /** Список номеров КПП для сброса. */
    val kpp: List<Int> = emptyList(),

    /** Признак высокоприоритетной передачи. */
    val highPriorityTransmit: Boolean = false,

    /**
     * Требуемый процент покрытия заявки (0 < x ≤ 100), при достижении которого заявка
     * считается выполненной (COMPLETED). Это **прогресс съёмки**, не путать с [importance]
     * (важность заявки для карты приоритетов ячеек). По умолчанию — 100%.
     */
    @field:DecimalMin(value = "0", inclusive = false)
    @field:DecimalMax("100")
    // Nullable с дефолтом: под Jackson 3 (MVC) FAIL_ON_NULL_FOR_PRIMITIVES включён по умолчанию,
    // и явный null от клиента падал бы на примитиве Double. Опциональное поле с дефолтом трактуем
    // null как «взять дефолт» — при маппинге в сущность коалесим к 100.0.
    val coverageRequiredPercent: Double? = 100.0,

    /** Параметры оптической съемки. */
    @field:Valid
    val optics: OpticsParamsDto? = null,

    /** Параметры радиолокационной съемки РСА. */
    @field:Valid
    val rsa: RsaParamsDto? = null,
) : StrictRequestApiDto() {
    @get:AssertTrue(message = "Должен быть указан хотя бы один блок съемки: optics или rsa")
    @get:JsonIgnore
    val hasSurveyPayload: Boolean
        get() = optics != null || rsa != null
}

/**
 * Ответ на создание заявки.
 */
data class CreateRequestResponseDto(
    /** Идентификатор заявки. */
    val id: UUID,

    /** Наименование задания. */
    val name: String,

    /** Статус заявки после приема. */
    val status: RequestStatusDto,

    /** Вычисленный тип заявки по наличию optics/rsa. */
    val surveyType: SurveyTypeDto,

    /** Важность заявки для расчета важности ячеек сетки. */
    val importance: Double,

    /** Дополнительное сообщение для клиента. */
    val message: String? = null,
)

/**
 * Полная read-модель заявки.
 */
data class RequestResponseDto(
    /** Идентификатор заявки. */
    val id: UUID,

    /** Наименование задания. */
    val name: String,

    /** Текущий статус заявки. */
    val status: RequestStatusDto,

    /** Вычисленный тип заявки. */
    val surveyType: SurveyTypeDto,

    /** Полная геометрия заявки в WKT. */
    val geometry: String,

    /** Важность заявки для расчета важности ячеек сетки. */
    val importance: Double,

    /** Начало временного окна выполнения заявки. */
    val beginDateTime: LocalDateTime,

    /** Окончание временного окна выполнения заявки. */
    val endDateTime: LocalDateTime,

    /** Список номеров КПП для сброса. */
    val kpp: List<Int> = emptyList(),

    /** Признак высокоприоритетной передачи. */
    val highPriorityTransmit: Boolean,

    /** Параметры оптической съемки, если заявка содержит optics. */
    val optics: OpticsParamsDto? = null,

    /** Параметры РСА-съемки, если заявка содержит rsa. */
    val rsa: RsaParamsDto? = null,

    /** Текущее состояние покрытия заявки. */
    val coverage: CoverageStateDto,

    /** Время создания заявки. */
    val createdAt: LocalDateTime,

    /** Время последнего обновления заявки. */
    val updatedAt: LocalDateTime,

    /** Время soft delete, если заявка удалена. */
    val deletedAt: LocalDateTime? = null,
)

/**
 * Краткая карточка заявки для list endpoint.
 */
data class RequestSummaryResponseDto(
    /** Идентификатор заявки. */
    val id: UUID,

    /** Наименование задания. */
    val name: String,

    /** Текущий статус заявки. */
    val status: RequestStatusDto,

    /** Вычисленный тип заявки. */
    val surveyType: SurveyTypeDto,

    /** Важность заявки для расчета важности ячеек сетки. */
    val importance: Double,

    /** Начало временного окна выполнения заявки. */
    val beginDateTime: LocalDateTime,

    /** Окончание временного окна выполнения заявки. */
    val endDateTime: LocalDateTime,

    /** Список номеров КПП для сброса. */
    val kpp: List<Int> = emptyList(),

    /** Признак высокоприоритетной передачи. */
    val highPriorityTransmit: Boolean,

    /** Текущий процент покрытия заявки. */
    val coveragePercent: Double,

    /** Время создания заявки. */
    val createdAt: LocalDateTime,

    /** Время последнего обновления заявки. */
    val updatedAt: LocalDateTime,

    /** Время soft delete, если заявка удалена. */
    val deletedAt: LocalDateTime? = null,
)

/**
 * Постраничный ответ list endpoint.
 */
data class RequestListResponseDto(
    /** Элементы текущей страницы. */
    val items: List<RequestSummaryResponseDto>,

    /** Номер страницы, начиная с 0. */
    val page: Int,

    /** Размер страницы. */
    val size: Int,

    /** Общее количество элементов. */
    val totalItems: Long,

    /** Общее количество страниц. */
    val totalPages: Int,
)

/**
 * Краткая карточка заявки с геометрией для GET /v1/requests/map.
 */
data class RequestMapItemDto(
    /** Идентификатор заявки. */
    val id: UUID,

    /** Наименование задания. */
    val name: String,

    /** Текущий статус заявки. */
    val status: RequestStatusDto,

    /** Вычисленный тип заявки. */
    val surveyType: SurveyTypeDto,

    /** Полная геометрия заявки в WKT. */
    val geometry: String,

    /** Важность заявки для расчета важности ячеек сетки. */
    val importance: Double,

    /** Начало временного окна выполнения заявки. */
    val beginDateTime: LocalDateTime,

    /** Окончание временного окна выполнения заявки. */
    val endDateTime: LocalDateTime,

    /** Список номеров КПП для сброса. */
    val kpp: List<Int> = emptyList(),

    /** Признак высокоприоритетной передачи. */
    val highPriorityTransmit: Boolean,

    /** Текущий процент покрытия заявки. */
    val coveragePercent: Double,

    /** Время создания заявки. */
    val createdAt: LocalDateTime,

    /** Время последнего обновления заявки. */
    val updatedAt: LocalDateTime,

    /** Время soft delete, если заявка удалена. */
    val deletedAt: LocalDateTime? = null,
)

/**
 * Постраничный список заявок с полной геометрией для GET /v1/requests/map.
 */
data class RequestMapPageDto(
    /** Элементы текущей страницы. */
    val items: List<RequestMapItemDto>,

    /** Номер страницы, начиная с 0. */
    val page: Int,

    /** Размер страницы. */
    val size: Int,

    /** Общее количество элементов. */
    val totalItems: Long,

    /** Общее количество страниц. */
    val totalPages: Int,
)

/**
 * Ячейка, связанная с заявкой через request_cells projection.
 */
data class RequestCellResponseDto(
    /** Номер ячейки сетки из earth_cells. */
    val cellNum: Long,

    /** Процент покрытия ячейки фрагментом заявки из request_cells. */
    val coveragePercent: Double,

    /** Snapshot важности заявки из request_cells. */
    val importance: Double,

    /** Контур фрагмента заявки внутри ячейки в WKT из request_cells. */
    val contour: String,
)

/**
 * Заявка с проекциями на ячейки сетки.
 */
data class RequestWithCellsResponseDto(
    /** Полная read-модель заявки нового /v1 API. */
    val request: RequestResponseDto,

    /** Проекции заявки на ячейки сетки из request_cells. */
    val cells: List<RequestCellResponseDto>,
)

/**
 * Ответ на soft delete заявки.
 */
data class DeleteRequestResponseDto(
    /** Идентификатор заявки. */
    val id: UUID,

    /** Время soft delete. */
    val deletedAt: LocalDateTime,
)

/**
 * Состояние покрытия заявки.
 */
data class CoverageStateDto(
    /** Требуемый процент покрытия, если он задан доменной моделью. */
    val requiredPercent: Double? = null,

    /** Текущий процент покрытия. */
    val currentPercent: Double,
)

/**
 * Параметры оптической съемки.
 */
data class OpticsParamsDto(
    /** Результат оптической съемки. */
    @field:NotNull
    val resultType: OpticsResultTypeDto,

    /** Требуемое разрешение съемки, метры. */
    @field:DecimalMin("0.1")
    val resolution: Double,

    /** Минимальная высота Солнца, градусы. */
    @field:DecimalMin("0")
    @field:DecimalMax("90")
    val sunAngleMin: Double = 10.0,

    /** Максимальная высота Солнца, градусы. */
    @field:DecimalMin("0")
    @field:DecimalMax("90")
    val sunAngleMax: Double = 90.0,

    /** Максимально допустимая облачность, проценты. */
    @field:DecimalMin("0")
    @field:DecimalMax("100")
    val clouds: Double = 100.0,
) : StrictRequestApiDto()

/**
 * Параметры радиолокационной съемки РСА.
 */
data class RsaParamsDto(
    /** Тип аппаратуры РСА. */
    @field:NotNull
    val resultType: RsaResultTypeDto,

    /** Признак интерферометрии. */
    val interferometry: Boolean = false,

    /** Тип поляризации. */
    @field:NotNull
    val polarisation: PolarisationDto,

    /** Требуемое разрешение съемки, метры. */
    @field:DecimalMin("0.1")
    val resolution: Double,
) : StrictRequestApiDto()

/**
 * Ошибка API.
 */
data class ErrorResponseDto(
    /** Машиночитаемый код ошибки. */
    val code: String,

    /** Человекочитаемое описание ошибки. */
    val message: String,

    /** Детали ошибки по полям. */
    val details: List<ErrorDetailDto>? = null,
)

/**
 * Деталь ошибки валидации.
 */
data class ErrorDetailDto(
    /** Поле, к которому относится ошибка. */
    val field: String,

    /** Описание ошибки поля. */
    val message: String,
)

/**
 * Query parameters для list endpoint.
 */
data class RequestListFilter(
    /** Фильтр по статусу заявки. */
    val status: RequestStatusDto? = null,

    /** Фильтр по вычисленному типу заявки. */
    val surveyType: SurveyTypeDto? = null,

    /** Фильтр по номеру КПП. */
    val kpp: Int? = null,

    /** Фильтр по признаку высокоприоритетной передачи. */
    val highPriorityTransmit: Boolean? = null,

    /** Нижняя граница beginDateTime, включительно. */
    val beginFrom: LocalDateTime? = null,

    /** Верхняя граница beginDateTime, включительно. */
    val beginTo: LocalDateTime? = null,

    /** Нижняя граница endDateTime, включительно. */
    val endFrom: LocalDateTime? = null,

    /** Верхняя граница endDateTime, включительно. */
    val endTo: LocalDateTime? = null,

    /** Включать soft-deleted заявки в результат. */
    val includeDeleted: Boolean = false,

    /** Номер страницы, начиная с 0. */
    @field:Min(0)
    val page: Int = 0,

    /** Размер страницы. */
    @field:Min(1)
    @field:Max(500)
    val size: Int = 50,

    /** Сортировка в формате field,direction. */
    val sort: String = "createdAt,desc",
)

/**
 * Доменный статус заявки в OpenAPI.
 */
enum class RequestStatusDto {
    /** Заявка прошла валидацию и принята сервисом. */
    ACCEPTED,

    /** Заявка актуальна для обработки. */
    ACTIVE,

    /** Заявка выполнена. */
    COMPLETED,

    /** Временное окно заявки истекло. */
    EXPIRED,

    /** Заявка мягко удалена. */
    DELETED,
}

/**
 * Вычисленный тип заявки по наличию optics/rsa.
 */
enum class SurveyTypeDto {
    /** В заявке указан только optics. */
    OPTICS,

    /** В заявке указан только rsa. */
    RSA,

    /** В заявке указаны optics и rsa. */
    COMBINED,
}

/**
 * Результат оптической съемки.
 */
enum class OpticsResultTypeDto {
    /** Панхроматическая съемка. */
    PANCHROMATIC,

    /** Мультиспектральная съемка. */
    MULTISPECTRAL,

    /** Паншарпенинг. */
    PANSHARPENING,
}

/**
 * Тип аппаратуры РСА.
 */
enum class RsaResultTypeDto {
    /** Spotlight режим. */
    SPOTLIGHT,

    /** Stripmap режим. */
    STRIPMAP,

    /** ScanSAR режим. */
    SCANSAR,
}

/**
 * Тип поляризации РСА.
 */
enum class PolarisationDto {
    /** Горизонтальная передача и горизонтальный прием. */
    HH,

    /** Вертикальная передача и вертикальный прием. */
    VV,
}
