package com.betriebsratkanzlei.legalconsenthub.application_form

import com.betriebsratkanzlei.legalconsenthub.application_form_version.ApplicationFormVersionService
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormInvalidStateException
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotCreatedException
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotDeletedException
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotFoundException
import com.betriebsratkanzlei.legalconsenthub.error.ApplicationFormNotUpdatedException
import com.betriebsratkanzlei.legalconsenthub.error.FormElementSectionNotFoundException
import com.betriebsratkanzlei.legalconsenthub.form_element.FormElementMapper
import com.betriebsratkanzlei.legalconsenthub.notification.NotificationService
import com.betriebsratkanzlei.legalconsenthub.user.UserService
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormDto
import com.betriebsratkanzlei.legalconsenthub_api.model.ApplicationFormStatus
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateApplicationFormDto
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateFormElementDto
import com.betriebsratkanzlei.legalconsenthub_api.model.CreateNotificationDto
import com.betriebsratkanzlei.legalconsenthub_api.model.NotificationType
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import java.util.UUID

@Service
class ApplicationFormService(
    private val applicationFormRepository: ApplicationFormRepository,
    private val applicationFormMapper: ApplicationFormMapper,
    private val formElementMapper: FormElementMapper,
    private val notificationService: NotificationService,
    private val versionService: ApplicationFormVersionService,
    private val userService: UserService,
) {
    fun createApplicationForm(createApplicationFormDto: CreateApplicationFormDto): ApplicationForm {
        val applicationForm = applicationFormMapper.toApplicationForm(createApplicationFormDto)
        val savedApplicationForm: ApplicationForm
        try {
            savedApplicationForm = applicationFormRepository.save(applicationForm)
        } catch (e: Exception) {
            throw ApplicationFormNotCreatedException(e)
        }

        val currentUser = userService.getCurrentUser()
        versionService.createVersion(savedApplicationForm, currentUser)

        return savedApplicationForm
    }

    fun getApplicationFormById(id: UUID): ApplicationForm =
        applicationFormRepository.findById(id).orElseThrow {
            ApplicationFormNotFoundException(id)
        }

    fun getApplicationForms(organizationId: String?): Page<ApplicationForm> {
        val pageable = PageRequest.of(0, 10)
        return applicationFormRepository.findAllByIsTemplateFalseAndOrganizationId(organizationId, pageable)
    }

    fun updateApplicationForm(applicationFormDto: ApplicationFormDto): ApplicationForm {
        val existingApplicationForm = getApplicationFormById(applicationFormDto.id)
        val applicationForm = applicationFormMapper.toApplicationForm(applicationFormDto)
        val updatedApplicationForm: ApplicationForm

        try {
            updatedApplicationForm = applicationFormRepository.save(applicationForm)
        } catch (e: Exception) {
            throw ApplicationFormNotUpdatedException(e, applicationFormDto.id)
        }

        val currentUser = userService.getCurrentUser()
        if (versionService.hasChanges(existingApplicationForm, updatedApplicationForm)) {
            versionService.createVersion(updatedApplicationForm, currentUser)
        }

        return updatedApplicationForm
    }

    fun deleteApplicationFormByID(id: UUID) {
        try {
            applicationFormRepository.deleteById(id)
        } catch (e: Exception) {
            throw ApplicationFormNotDeletedException(e)
        }
    }

    fun submitApplicationForm(id: UUID): ApplicationForm {
        val applicationForm = getApplicationFormById(id)

        if (applicationForm.status != ApplicationFormStatus.DRAFT) {
            throw ApplicationFormInvalidStateException(
                applicationFormId = id,
                currentState = applicationForm.status,
                expectedState = ApplicationFormStatus.DRAFT,
                operation = "submit",
            )
        }

        applicationForm.status = ApplicationFormStatus.SUBMITTED

        val savedApplicationForm =
            try {
                applicationFormRepository.save(applicationForm)
            } catch (e: Exception) {
                throw ApplicationFormNotUpdatedException(e, id)
            }

        val currentUser = userService.getCurrentUser()
        versionService.createVersion(savedApplicationForm, currentUser)

        createNotificationForOrganization(savedApplicationForm)

        return savedApplicationForm
    }

    private fun createNotificationForOrganization(applicationForm: ApplicationForm) {
        val title = "Neuer Mitbestimmungsantrag eingereicht"
        val message =
            "Ein neuer Mitbestimmungsantrag '${applicationForm.name}' wurde von " +
                "${applicationForm.createdBy.name} eingereicht."
        val clickTarget = "/application-forms/${applicationForm.id}/0"

        val createNotificationDto =
            CreateNotificationDto(
                title = title,
                message = message,
                clickTarget = clickTarget,
                recipientId = null,
                targetRoles = null,
                type = NotificationType.INFO,
                organizationId = applicationForm.organizationId,
            )

        notificationService.createNotificationForOrganization(createNotificationDto)
    }

    fun addFormElementToSection(
        applicationFormId: UUID,
        sectionId: UUID,
        createFormElementDto: CreateFormElementDto,
        position: Int,
    ): ApplicationForm {
        val applicationForm = getApplicationFormById(applicationFormId)

        val section =
            applicationForm.formElementSections
                .find { it.id == sectionId }
                ?: throw FormElementSectionNotFoundException(sectionId)

        val newFormElement = formElementMapper.toFormElement(createFormElementDto, section)

        if (position >= 0 && position < section.formElements.size) {
            section.formElements.add(position, newFormElement)
        } else {
            section.formElements.add(newFormElement)
        }

        val updatedApplicationForm =
            try {
                applicationFormRepository.save(applicationForm)
            } catch (e: Exception) {
                throw ApplicationFormNotUpdatedException(e, applicationFormId)
            }

        return updatedApplicationForm
    }
}
