import { Category } from '@backend/domain'
import { CategoryRepository } from '@backend/repository'
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'
import { Transactional } from 'typeorm-transactional'
import { CategoryMapper } from '../mappers'
import { CategoryResponse, CreateCategoryRequest, UpdateCategoryRequest, UpdateSortOrderRequest } from '../types'

@Injectable()
export class CategoryService {
	constructor(private readonly _categoryRepository: CategoryRepository) {}

	async findAll(): Promise<CategoryResponse[]> {
		const categories = await this._categoryRepository.find({ relations: { parentCategory: true } })
		return categories.map((category) => CategoryMapper.categoryToResponse(category))
	}

	async findById(id: string): Promise<CategoryResponse> {
		const category = await this._categoryRepository.findOne({ where: { id }, relations: { parentCategory: true } })
		if (!category) {
			throw new NotFoundException('Category not found')
		}

		return CategoryMapper.categoryToResponse(category)
	}

	async save(request: CreateCategoryRequest): Promise<CategoryResponse> {
		const category: Partial<Category> = {
			name: request.name,
			sortOrder: request.sortOrder,
		}

		if (request.parentCategoryId) {
			const parentCategory = await this._categoryRepository.findOne({
				where: { id: request.parentCategoryId },
				select: { id: true },
			})
			if (!parentCategory) {
				throw new NotFoundException('Parent category not found')
			}
			category.parentCategory = parentCategory
		}

		const entity = await this._categoryRepository.save(category)
		return CategoryMapper.categoryToResponse(entity)
	}

	async updateById(id: string, request: UpdateCategoryRequest): Promise<CategoryResponse> {
		if (id !== request.id) {
			throw new BadRequestException()
		}

		const existing = await this._categoryRepository.findOne({ where: { id }, relations: { parentCategory: true } })
		if (!existing) {
			throw new NotFoundException('Category not found')
		}

		existing.name = request.name
		existing.sortOrder = request.sortOrder

		if (request.parentCategoryId) {
			const parentCategory = await this._categoryRepository.findOne({
				where: { id: request.parentCategoryId },
				select: { id: true },
			})
			if (!parentCategory) {
				throw new NotFoundException('Parent category not found')
			}
			existing.parentCategory = parentCategory
		}

		await this._categoryRepository.update({ id }, existing)
		return CategoryMapper.categoryToResponse(existing)
	}

	async deleteById(id: string): Promise<void> {
		const exists = await this._categoryRepository.exist({ where: { id } })
		if (!exists) {
			throw new NotFoundException('Category not found')
		}

		await this._categoryRepository.softDelete({ id })
	}

	async toggleArchive(id: string, isArchived: boolean): Promise<void> {
		const exists = await this._categoryRepository.exist({ where: { id } })
		if (!exists) {
			throw new NotFoundException('Category not found')
		}

		await this._categoryRepository.update({ id }, { isArchived })
	}

	async toggleVisible(id: string, isVisible: boolean): Promise<void> {
		const exists = await this._categoryRepository.exist({ where: { id } })
		if (!exists) {
			throw new NotFoundException('Category not found')
		}

		await this._categoryRepository.update({ id }, { isVisible })
	}

	@Transactional()
	async updateSortOrder(request: UpdateSortOrderRequest[]): Promise<void> {
		const sortOrders = request.map((x) => x.sortOrder)
		if (new Set(sortOrders).size !== sortOrders.length) {
			throw new BadRequestException('Sort orders must be unique')
		}

		for (const { id, sortOrder } of request) {
			await this._categoryRepository.update({ id }, { sortOrder })
		}
	}
}
