import { BaseProvider } from '#templates/base/provider';
import { Technician } from '#models/Technician';
import { User } from '#models/User';
import { UserAuth } from '#models/UserAuth';
import { Role } from '#models/Role';
import { UserRole as UserRoleModel } from '#models/UserRole';
import { UserProvider } from '#providers/UserProvider';
import { PasswordService } from '#services/auth/passwordService';
import { UserRoleEnum } from '#constants/index';
import { GenericError } from '#interfaces/error/generic';
import { normalizePhone } from '#utils/stringUtils';
import { Op } from 'sequelize';
import { randomUUID } from 'crypto';
import { AuditLogService } from '#services/audit/auditLogService';

export class TechnicianProvider extends BaseProvider<Technician> {
	public static instance: TechnicianProvider;

	public static getInstance(): TechnicianProvider {
		TechnicianProvider.instance ??= new TechnicianProvider();
		return TechnicianProvider.instance;
	}

	public static get model() {
		return Technician;
	}

	constructor() {
		super('Technician');
	}

	async createWithUser(body: any, options?: { transaction?: any }): Promise<any> {
		const { first_name, last_name, phone, email, ...technicianData } = body;
		const requestId = randomUUID();
		const traceId = randomUUID();

		return this.executeInTransaction(async (tx) => {
			// Create technician row (no personal info columns)
			const technician = await this.create(technicianData, { ...options, transaction: tx });

			const normalizedPhone = normalizePhone(phone);
			if (!normalizedPhone) {
				throw new GenericError('INVALID_PHONE_FORMAT');
			}

			const passwordHash = await PasswordService.hashPassword('Bekind@123');
			const userId = randomUUID();
			const userEmail = email || `${normalizedPhone}@bekind.com`;

			// Create user account
			await User.create(
				{
					id: userId,
					email: userEmail,
					phone: normalizedPhone,
					username: normalizedPhone,
					first_name: first_name || null,
					last_name: last_name || null,
					status: 'active',
					created_at: new Date(),
					updated_at: new Date(),
				} as any,
				{ transaction: tx },
			);

			// Create user auth record
			await UserAuth.create(
				{
					id: randomUUID(),
					user_id: userId,
					password_hash: passwordHash,
					password_changed_at: new Date(),
					login_attempts: 0,
					created_at: new Date(),
					updated_at: new Date(),
				} as any,
				{ transaction: tx },
			);

			// Assign default role
			const defaultRole = await Role.findOne({
				where: { name: UserRoleEnum.USER },
				transaction: tx,
			});
			if (!defaultRole) {
				throw new GenericError('DEFAULT_ROLE_NOT_FOUND');
			}

			await UserRoleModel.create(
				{
					id: randomUUID(),
					user_id: userId,
					role_id: defaultRole.id,
					assigned_at: new Date(),
					updated_at: new Date(),
				} as any,
				{ transaction: tx },
			);

			// Link user to technician
			await technician.update({ user_id: userId, updated_at: new Date() }, { transaction: tx });

			// Audit log
			await AuditLogService.enqueueSystemAudit({
				requestId,
				traceId,
				actorId: technicianData.created_by || null,
				actorName: technicianData.created_by ? undefined : 'System',
				actorEmail: technicianData.created_by ? undefined : 'system@bekind.vn',
				actorRole: technicianData.created_by ? undefined : 'SYSTEM',
				action: 'USER_ACCOUNT_CREATED',
				module: 'TECHNICIAN',
				entityId: userId,
				entityType: 'User',
				description: `User account created for technician ${first_name || ''} ${last_name || ''}`.trim(),
				severity: 'MEDIUM',
				payload: {
					userId,
					technicianId: technician.get('id'),
					username: normalizedPhone,
				},
			} as any);

			return {
				...technician.toJSON(),
				user: {
					id: userId,
					first_name: first_name || null,
					last_name: last_name || null,
					email: userEmail,
					phone: normalizedPhone,
					username: normalizedPhone,
					status: 'active',
				},
			};
		}, options?.transaction);
	}

	public async getByIdWithUser(options: { id: string }) {
		const technician = await this.getById(options);
		if (!technician) return null;

		const userData = technician.user_id ? await UserProvider.getInstance().getById({ id: technician.user_id }) : null;

		const fullName = userData ? `${userData.first_name || ''} ${userData.last_name || ''}`.trim() : null;

		return {
			...technician.toJSON(),
			full_name: fullName,
			phone: userData?.phone || null,
			email: userData?.email || null,
		};
	}

	public async getAllWithUser(options?: any) {
		const technicians = await this.getAll(options);
		if (!technicians.rows || technicians.rows.length === 0) {
			return technicians;
		}

		const userIds = technicians.rows.map((t: any) => t.user_id).filter((id: string | null) => id);
		const userProvider = UserProvider.getInstance();
		const users =
			userIds.length > 0 ? await userProvider.getAll({ where: { id: { [Op.in]: userIds } } as any }) : { rows: [] };
		const userMap = new Map(users.rows.map((u: any) => [u.id, u]));

		const techniciansWithUser = technicians.rows.map((technician: any) => {
			const userData = technician.user_id ? userMap.get(technician.user_id) : null;
			const fullName = userData ? `${userData.first_name || ''} ${userData.last_name || ''}`.trim() : null;

			return {
				...technician.toJSON(),
				full_name: fullName,
				phone: userData?.phone || null,
				email: userData?.email || null,
			};
		});

		return {
			count: technicians.count,
			rows: techniciansWithUser,
			page: technicians.page,
			pageSize: technicians.pageSize,
		};
	}
}
