import datetime
from abc import abstractmethod
from typing import Any, Literal

import msgspec
from rt.rest2 import Attachment

from csirtmu.self_service_backend import models
from csirtmu.self_service_backend.enums import NetboxQueryType

#############################################
#                RT structs                 #
#############################################


class ImageAttachment(msgspec.Struct):
    name: str
    content: str
    type: str


class FormData(msgspec.Struct):
    fields: dict[str, Any]
    attachments: list[ImageAttachment]


class RTFormattedData(msgspec.Struct):
    content: str
    attachments: list[Attachment]


#############################################
#              Input Structs                #
#############################################


class TranslationInput(msgspec.Struct):
    text: str
    language: Literal["EN", "CZ"]

    def to_model(self) -> models.Translation:
        return models.Translation(
            contents=self.text,
            language=models.Language(self.language),
        )


class TextInput(msgspec.Struct):
    translations: list[TranslationInput]

    def to_model(self) -> models.Text:
        return models.Text(translations=[t.to_model() for t in self.translations])

    @staticmethod
    def from_strings(text_en: str, text_cz: str) -> "TextInput":
        return TextInput(
            translations=[
                TranslationInput(text=text_en, language="EN"),
                TranslationInput(text=text_cz, language="CZ"),
            ]
        )


class StepInput(msgspec.Struct, kw_only=True):
    id: int | None = None
    title: TextInput
    prev_id: int | None
    description: TextInput | None = None

    @abstractmethod
    def to_model(self) -> models.Step: ...


class TextStepInput(StepInput, tag="TextStep", tag_field="kind"):
    text: TextInput

    def to_model(self) -> models.TextStep:
        return models.TextStep(
            title=self.title.to_model(),
            prev_id=self.prev_id,
            description=self.description.to_model() if self.description is not None else None,
            text=self.text.to_model(),
        )


class LinkStepInput(StepInput, tag="LinkStep", tag_field="kind"):
    link: str

    def to_model(self) -> models.LinkStep:
        return models.LinkStep(
            title=self.title.to_model(),
            prev_id=self.prev_id,
            description=self.description.to_model() if self.description is not None else None,
            link=self.link,
        )


class FormFieldInput(msgspec.Struct, kw_only=True):
    label: TextInput
    position: int

    @abstractmethod
    def to_model(self) -> models.FormField: ...


class TextInputInput(FormFieldInput, tag="TextInput", tag_field="kind"):
    value: str | None = None
    max_chars: int | None = None
    type: Literal["default", "password"] = "default"
    help: str | None = None
    autocomplete: str | None = None
    placeholder: TextInput | None = None
    label_visibility: Literal["visible", "hidden", "collapsed"] = "visible"
    icon: str | None = None

    def to_model(self) -> models.TextInput:
        placeholder = None if self.placeholder is None else self.placeholder.to_model()
        return models.TextInput(
            position=self.position,
            label=self.label.to_model(),
            value=self.value,
            max_chars=self.max_chars,
            type=self.type,
            help=self.help,
            autocomplete=self.autocomplete,
            placeholder=placeholder,
            label_visibility=self.label_visibility,
            icon=self.icon,
        )


class SelectboxInput(FormFieldInput, tag="Selectbox", tag_field="kind"):
    options: str | list[str]
    index: int | None = None
    help: str | None = None
    placeholder: TextInput | None = None
    label_visibility: Literal["visible", "hidden", "collapsed"] = "visible"
    accept_new_options: bool = False

    def to_model(self) -> models.Selectbox:
        placeholder = None if self.placeholder is None else self.placeholder.to_model()
        return models.Selectbox(
            position=self.position,
            label=self.label.to_model(),
            options=self.options.split(";") if isinstance(self.options, str) else self.options,
            index=self.index,
            help=self.help,
            placeholder=placeholder,
            label_visibility=self.label_visibility,
            accept_new_options=self.accept_new_options,
        )


class MultiselectInput(FormFieldInput, tag="Multiselect", tag_field="kind"):
    options: str | list[str]
    default: list[str] | None = None
    help: str | None = None
    max_selections: int | None = None
    placeholder: TextInput | None = None
    label_visibility: Literal["visible", "hidden", "collapsed"] = "visible"
    accept_new_options: bool = False

    def to_model(self) -> models.Multiselect:
        return models.Multiselect(
            position=self.position,
            label=self.label.to_model(),
            options=self.options.split(";") if isinstance(self.options, str) else self.options,
            default=self.default,
            help=self.help,
            max_selections=self.max_selections,
            placeholder=self.placeholder,
            label_visibility=self.label_visibility,
            accept_new_options=self.accept_new_options,
        )


class TextAreaInput(FormFieldInput, tag="TextArea", tag_field="kind"):
    value: str | None = None
    max_chars: int | None = None
    height: int | None = None
    help: str | None = None
    placeholder: TextInput | None = None
    label_visibility: Literal["visible", "hidden", "collapsed"] = "visible"

    def to_model(self) -> models.TextArea:
        placeholder = None if self.placeholder is None else self.placeholder.to_model()
        return models.TextArea(
            position=self.position,
            label=self.label.to_model(),
            value=self.value,
            max_chars=self.max_chars,
            height=self.height,
            help=self.help,
            placeholder=placeholder,
            label_visibility=self.label_visibility,
        )


class NumberInputInput(FormFieldInput):
    format: str | None = None
    help: str | None = None
    label_visibility: Literal["visible", "hidden", "collapsed"] = "visible"
    icon: str | None = None

    @abstractmethod
    def to_model(self) -> models.FormField: ...


class NumberInputIntInput(NumberInputInput, tag="NumberInputInt", tag_field="kind"):
    value: int | None = None
    min_value: int | None = None
    max_value: int | None = None
    step: int | None = None
    placeholder: int | None = None

    def to_model(self) -> models.NumberInputInt:
        return models.NumberInputInt(
            position=self.position,
            label=self.label.to_model(),
            value=self.value,
            format=self.format,
            min_value=self.min_value,
            max_value=self.max_value,
            step=self.step,
            help=self.help,
            placeholder=self.placeholder,
            label_visibility=self.label_visibility,
            icon=self.icon,
        )


class NumberInputFloatInput(NumberInputInput, tag="NumberInputFloat", tag_field="kind"):
    value: float | None = None
    min_value: float | None = None
    max_value: float | None = None
    step: float | None = None
    placeholder: float | None = None

    def to_model(self) -> models.NumberInputFloat:
        return models.NumberInputFloat(
            position=self.position,
            label=self.label.to_model(),
            value=self.value,
            format=self.format,
            min_value=self.min_value,
            max_value=self.max_value,
            step=self.step,
            help=self.help,
            placeholder=self.placeholder,
            label_visibility=self.label_visibility,
            icon=self.icon,
        )


class CheckboxInput(FormFieldInput, tag="Checkbox", tag_field="kind"):
    value: bool = False
    help: str | None = None
    label_visibility: Literal["visible", "hidden", "collapsed"] = "visible"

    def to_model(self) -> models.Checkbox:
        return models.Checkbox(
            position=self.position,
            label=self.label.to_model(),
            value=self.value,
            help=self.help,
            label_visibility=self.label_visibility,
        )


class RadioInput(FormFieldInput, tag="Radio", tag_field="kind"):
    options: str | list[str]
    index: int | None = 0
    help: str | None = None
    horizontal: bool = False
    captions: str | list[str] | None = None
    label_visibility: Literal["visible", "hidden", "collapsed"] = "visible"

    def to_model(self) -> models.Radio:
        if self.captions is None:
            captions = None
        elif isinstance(self.captions, str):
            captions = self.captions.split(";")
        else:
            captions = self.captions
        return models.Radio(
            position=self.position,
            label=self.label.to_model(),
            options=self.options.split(";") if isinstance(self.options, str) else self.options,
            index=self.index,
            help=self.help,
            horizontal=self.horizontal,
            captions=captions,
            label_visibility=self.label_visibility,
        )


class DateInputInput(FormFieldInput, tag="DateInput", tag_field="kind"):
    value: datetime.date | None = None
    min_value: datetime.date | None = None
    max_value: datetime.date | None = None
    format: str | None = None
    help: str | None = None
    label_visibility: Literal["visible", "hidden", "collapsed"] = "visible"

    def to_model(self) -> models.DateInput:
        return models.DateInput(
            position=self.position,
            label=self.label.to_model(),
            value=self.value,
            min_value=self.min_value,
            max_value=self.max_value,
            format=self.format,
            help=self.help,
            label_visibility=self.label_visibility,
        )


class TimeInputInput(FormFieldInput, tag="TimeInput", tag_field="kind"):
    value: datetime.time | None = None
    help: str | None = None
    step: int | None = None
    label_visibility: Literal["visible", "hidden", "collapsed"] = "visible"

    def to_model(self) -> models.TimeInput:
        return models.TimeInput(
            position=self.position,
            label=self.label.to_model(),
            value=self.value,
            step=self.step,
            help=self.help,
            label_visibility=self.label_visibility,
        )


class FileUploadInput(FormFieldInput, tag="FileUpload", tag_field="kind"):
    type: str | list[str] | None = None
    accept_multiple_files: bool = False
    help: str | None = None
    label_visibility: Literal["visible", "hidden", "collapsed"] = "visible"

    def to_model(self) -> models.FileUploader:
        if self.type is None:
            types = None
        elif isinstance(self.type, str):
            types = self.type.split(";")
        else:
            types = self.type
        return models.FileUploader(
            position=self.position,
            label=self.label.to_model(),
            type=types,
            accept_multiple_files=self.accept_multiple_files,
            help=self.help,
            label_visibility=self.label_visibility,
        )


class TextFieldInput(FormFieldInput, tag="TextField", tag_field="kind"):
    text: TextInput

    def to_model(self) -> models.TextField:
        return models.TextField(
            position=self.position,
            label=self.label.to_model(),
            text=self.text.to_model(),
        )


class CompositeFormFieldInput(FormFieldInput, tag="CompositeFormField", tag_field="kind"):
    @abstractmethod
    def to_model(self) -> models.FormField: ...


class OneRequiredInput(CompositeFormFieldInput, tag="OneRequired", tag_field="kind"):
    fields: list[CheckboxInput]

    def to_model(self) -> models.OneRequired:
        model = models.OneRequired(
            position=self.position,
            label=self.label.to_model(),
            fields=[],
        )
        for field in self.fields:
            f: models.Checkbox = field.to_model()
            model.fields.append(f)
        return model


class OnSelectionInput(CompositeFormFieldInput, tag="OnSelection", tag_field="kind"):
    selection_field: (
        TextInputInput
        | SelectboxInput
        | MultiselectInput
        | TextAreaInput
        | NumberInputIntInput
        | NumberInputFloatInput
        | CheckboxInput
        | RadioInput
        | DateInputInput
        | TimeInputInput
        | FileUploadInput
    )
    mapping: dict[
        str,
        list[
            TextInputInput
            | SelectboxInput
            | MultiselectInput
            | TextAreaInput
            | NumberInputIntInput
            | NumberInputFloatInput
            | CheckboxInput
            | RadioInput
            | DateInputInput
            | TimeInputInput
            | FileUploadInput
            | TextFieldInput
        ],
    ]

    def to_model(self) -> models.OnSelection:
        return models.OnSelection(
            position=self.position,
            label=self.label.to_model(),
            selection_field=self.selection_field.to_model(),
            mapping=[
                models.SelectionMapping(option=opt, fields=[field.to_model() for field in fields])
                for opt, fields in self.mapping.items()
            ],
        )


class DynamicSectionInput(CompositeFormFieldInput, tag="DynamicSection", tag_field="kind"):
    fields: list[
        TextInputInput
        | SelectboxInput
        | MultiselectInput
        | TextAreaInput
        | NumberInputIntInput
        | NumberInputFloatInput
        | CheckboxInput
        | RadioInput
        | DateInputInput
        | TimeInputInput
        | FileUploadInput
        | TextFieldInput
    ]
    default_amount: int = 0

    def to_model(self) -> models.DynamicSection:
        model = models.DynamicSection(
            position=self.position,
            label=self.label.to_model(),
            default_amount=self.default_amount,
            fields=[],
        )
        for field in self.fields:
            f = field.to_model()
            model.fields.append(f)
        return model


class FormStepInput(StepInput, tag="FormStep", tag_field="kind"):
    fields: list[
        TextInputInput
        | SelectboxInput
        | MultiselectInput
        | TextAreaInput
        | NumberInputIntInput
        | NumberInputFloatInput
        | CheckboxInput
        | RadioInput
        | DateInputInput
        | TimeInputInput
        | FileUploadInput
        | TextFieldInput
        | OneRequiredInput
        | OnSelectionInput
        | DynamicSectionInput
    ]
    clear_on_submit: bool = False
    border: bool = True

    def to_model(self) -> models.FormStep:
        return models.FormStep(
            title=self.title.to_model(),
            prev_id=self.prev_id,
            description=self.description.to_model() if self.description is not None else None,
            clear_on_submit=self.clear_on_submit,
            border=self.border,
            fields=[f.to_model() for f in self.fields],
        )


class FormSubmitInput(msgspec.Struct):
    title: str
    requestor: str
    data: FormData


class NetboxSearchStepInput(StepInput, tag="NetboxSearchStep", tag_field="kind"):
    query_type: NetboxQueryType

    @abstractmethod
    def to_model(self) -> models.NetboxSearchStep:
        return models.NetboxSearchStep(
            title=self.title.to_model(),
            prev_id=self.prev_id,
            description=self.description.to_model() if self.description is not None else None,
            query_type=self.query_type,
        )


class OutlineSearchStepInput(StepInput, tag="OutlineSearchStep", tag_field="kind"):
    def to_model(self) -> models.OutlineSearchStep:
        return models.OutlineSearchStep(
            title=self.title.to_model(),
            prev_id=self.prev_id,
            description=self.description.to_model() if self.description is not None else None,
        )
