import { JWT_CONSTANTS, SessionStore } from '@backend/auth'
import { MessagingTokenPlatform, User } from '@backend/domain'
import { TableSubscriberRepository, UserCredentialsRepository, UserRepository } from '@backend/repository'
import {
	BadRequestException,
	ConflictException,
	ForbiddenException,
	Injectable,
	NotFoundException,
	UnauthorizedException,
} from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { Transactional } from 'typeorm-transactional'
import { DASHBOARD_URL } from '../../../config'
import { EmailService, MAILER_TEMPLATES, SendMailOptions } from '../../../global'
import { UserMapper } from '../mappers'
import { CreateUserRequest, UpdateMessagingTokenRequest, UpdateUserRequest, UserResponse } from '../types'

@Injectable()
export class UserService {
	constructor(
		private readonly _jwtService: JwtService,
		private readonly _emailService: EmailService,
		private readonly _userRepository: UserRepository,
		private readonly _userCredentialsRepository: UserCredentialsRepository,
		private readonly _tableSubscriberRepository: TableSubscriberRepository,
	) {}

	async findAll(): Promise<UserResponse[]> {
		const users = await this._userRepository.find()
		return users.map((user) => UserMapper.entityToResponse(user))
	}

	async findById(id: string): Promise<UserResponse> {
		const user = await this._userRepository.findOne({ where: { id } })
		if (!user) {
			throw new NotFoundException('User not found')
		}

		return UserMapper.entityToResponse(user)
	}

	async create(request: CreateUserRequest): Promise<UserResponse> {
		const exists = await this._userRepository.exist({ where: { email: request.email } })
		if (exists) {
			throw new ConflictException('A user with same email already exists')
		}

		const user: Partial<User> = {
			firstName: request.firstName,
			lastName: request.lastName,
			email: request.email,
		}
		const entity = await this._userRepository.save(user)

		const token = await this._jwtService.signAsync(
			{ email: request.email },
			{ secret: JWT_CONSTANTS.SECRET, expiresIn: '4h' },
		)

		await this.sendCreatePasswordEmail(request.email, token)

		return UserMapper.entityToResponse(entity)
	}

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

		const existing = await this._userRepository.findOne({ where: { id } })
		if (!existing) {
			throw new NotFoundException('User not found')
		}

		await this._userRepository.update({ id }, request)
		return UserMapper.entityToResponse(existing)
	}

	async updateMessagingTokenById(userId: string, request: UpdateMessagingTokenRequest): Promise<void> {
		const user = await this._userRepository.findOne({ where: { id: userId }, select: { messagingToken: {} } })
		if (!user) {
			throw new NotFoundException('User not found')
		}

		const updatedUser: Partial<User> = {
			messagingToken: {
				web: request.platform === MessagingTokenPlatform.web ? request.token : user.messagingToken?.web,
				mobile: request.platform === MessagingTokenPlatform.mobile ? request.token : user.messagingToken?.mobile,
			},
		}

		await this._userRepository.update({ id: userId }, updatedUser)
	}

	@Transactional()
	async deleteById(id: string): Promise<void> {
		const user = await this._userRepository.findOne({ where: { id }, relations: { credentials: true } })
		if (!user) {
			throw new NotFoundException('User not found')
		}

		if (user.isAdmin) {
			throw new ForbiddenException('Admin user cannot be deleted')
		}

		await this._userCredentialsRepository.delete({ id: user.credentials?.id })
		await this._tableSubscriberRepository.delete({ user: { id } })

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

	private async sendCreatePasswordEmail(email: string, token: string): Promise<void> {
		const mail: SendMailOptions = {
			subject: 'Create your account - KADiCon',
			to: email,
			template: MAILER_TEMPLATES.AUTH.NEW_USER_PASSWORD.DEFAULT,
			context: {
				kadi_app_url: DASHBOARD_URL,
				reset_url: `${DASHBOARD_URL}/reset-password?token=${token}`,
			},
		}

		await this._emailService.send(mail)
	}

	/**
	 * Retrieves the profile information for the currently logged in user.
	 *
	 * @returns A Promise containing the user's profile information
	 * @throws UnauthorizedException if the user is not logged in
	 */
	async getProfile(): Promise<UserResponse> {
		const userId = SessionStore.userId
		if (!userId) {
			throw new UnauthorizedException()
		}

		const user = await this._userRepository.findOne({ where: { id: userId } })
		if (!user) {
			throw new UnauthorizedException()
		}

		return {
			id: user.id,
			firstName: user.firstName,
			lastName: user.lastName,
			fullName: `${user.firstName} ${user.lastName}`,
			email: user.email,
			emailVerified: user.emailVerified,
			preferedLanguage: user.preferedLanguage,
			createdAt: user.createdAt,
		}
	}
}
