'use strict';

const { Op } = require('sequelize');
const projectsRepository = require('./repository');
const { validateUid, validateName, formatDate } = require('./validation');
const { NotFoundError, ValidationError } = require('../../shared/errors');
const { validateTagName } = require('../tags/tagsService');
const permissionsService = require('../../services/permissionsService');
const { sortTags } = require('../tasks/core/serializers');
const {
    getSafeTimezone,
    processDueDateForResponse,
} = require('../../utils/timezone-utils');
const { uid: generateUid } = require('../../utils/uid');
const { extractUidFromSlug } = require('../../utils/slug-utils');
const { logError } = require('../../services/logService');

/**
 * Update project tags.
 */
async function updateProjectTags(project, tagsData, userId) {
    if (!tagsData) return;

    const validTagNames = [];
    const invalidTags = [];

    for (const tag of tagsData) {
        const validation = validateTagName(tag.name);
        if (validation.valid) {
            if (!validTagNames.includes(validation.name)) {
                validTagNames.push(validation.name);
            }
        } else {
            invalidTags.push({ name: tag.name, error: validation.error });
        }
    }

    if (invalidTags.length > 0) {
        throw new ValidationError(
            `Invalid tag names: ${invalidTags.map((t) => `"${t.name}" (${t.error})`).join(', ')}`
        );
    }

    if (validTagNames.length === 0) {
        await project.setTags([]);
        return;
    }

    const existingTags = await projectsRepository.findTagsByNames(
        userId,
        validTagNames
    );
    const existingTagNames = existingTags.map((tag) => tag.name);
    const newTagNames = validTagNames.filter(
        (name) => !existingTagNames.includes(name)
    );

    const createdTags = await Promise.all(
        newTagNames.map((name) => projectsRepository.createTag(name, userId))
    );

    await project.setTags([...existingTags, ...createdTags]);
}

/**
 * Calculate task status counts.
 */
function calculateTaskStatus(tasks) {
    const taskList = tasks || [];
    return {
        total: taskList.length,
        done: taskList.filter((t) => t.status === 2).length,
        in_progress: taskList.filter((t) => t.status === 1).length,
        not_started: taskList.filter((t) => t.status === 0).length,
    };
}

class ProjectsService {
    /**
     * Get all projects for a user with filters.
     */
    async getAll(userId, query) {
        const {
            status,
            state,
            active,
            pin_to_sidebar,
            area_id,
            area,
            grouped,
        } = query;
        const statusFilter = status || state;

        let whereClause = await permissionsService.ownershipOrPermissionWhere(
            'project',
            userId
        );

        if (statusFilter && statusFilter !== 'all') {
            if (Array.isArray(statusFilter)) {
                whereClause.status = { [Op.in]: statusFilter };
            } else {
                whereClause.status = statusFilter;
            }
        }

        if (active === 'true') {
            whereClause.status = {
                [Op.in]: ['planned', 'in_progress', 'waiting'],
            };
        } else if (active === 'false') {
            whereClause.status = { [Op.in]: ['not_started', 'done'] };
        }

        if (pin_to_sidebar === 'true') {
            whereClause.pin_to_sidebar = true;
        } else if (pin_to_sidebar === 'false') {
            whereClause.pin_to_sidebar = false;
        }

        if (area && area !== '') {
            const uid = extractUidFromSlug(area);
            if (uid) {
                const areaRecord = await projectsRepository.findAreaByUid(uid);
                if (areaRecord) {
                    whereClause = {
                        [Op.and]: [whereClause, { area_id: areaRecord.id }],
                    };
                }
            }
        } else if (area_id && area_id !== '') {
            whereClause = { [Op.and]: [whereClause, { area_id }] };
        }

        const projects =
            await projectsRepository.findAllWithFilters(whereClause);

        const projectUids = projects.map((p) => p.uid).filter(Boolean);
        const shareCountMap =
            await projectsRepository.getShareCounts(projectUids);

        const enhancedProjects = projects.map((project) => {
            const taskStatus = calculateTaskStatus(project.Tasks);
            const projectJson = project.toJSON();
            const shareCount = shareCountMap[project.uid] || 0;

            const isActiveStatus =
                project.status === 'planned' ||
                project.status === 'in_progress';
            const activeTaskCount =
                taskStatus.in_progress + taskStatus.not_started;
            const isStalled = isActiveStatus && activeTaskCount === 0;

            return {
                ...projectJson,
                tags: sortTags(projectJson.Tags),
                due_date_at: formatDate(project.due_date_at),
                task_status: taskStatus,
                completion_percentage:
                    taskStatus.total > 0
                        ? Math.round((taskStatus.done / taskStatus.total) * 100)
                        : 0,
                user_uid: projectJson.User?.uid,
                share_count: shareCount,
                is_shared: shareCount > 0,
                is_stalled: isStalled,
            };
        });

        if (grouped === 'true') {
            const groupedProjects = {};
            enhancedProjects.forEach((project) => {
                const areaName = project.Area ? project.Area.name : 'No Area';
                if (!groupedProjects[areaName]) {
                    groupedProjects[areaName] = [];
                }
                groupedProjects[areaName].push(project);
            });
            return groupedProjects;
        }

        return { projects: enhancedProjects };
    }

    /**
     * Get project by UID.
     */
    async getByUid(uid, userTimezone) {
        const validatedUid = validateUid(uid);
        const project =
            await projectsRepository.findByUidWithIncludes(validatedUid);

        if (!project) {
            throw new NotFoundError('Project not found');
        }

        const safeTimezone = getSafeTimezone(userTimezone);
        const projectJson = project.toJSON();

        const normalizedTasks = projectJson.Tasks
            ? projectJson.Tasks.map((task) => {
                  const normalizedTask = {
                      ...task,
                      tags: sortTags(task.Tags),
                      subtasks: (task.Subtasks || []).map((subtask) => ({
                          ...subtask,
                          tags: sortTags(subtask.Tags),
                      })),
                      due_date: processDueDateForResponse(
                          task.due_date,
                          safeTimezone
                      ),
                  };
                  delete normalizedTask.Tags;
                  delete normalizedTask.Subtasks;
                  return normalizedTask;
              })
            : [];

        const normalizedNotes = projectJson.Notes
            ? projectJson.Notes.map((note) => {
                  const normalizedNote = { ...note, tags: sortTags(note.Tags) };
                  delete normalizedNote.Tags;
                  return normalizedNote;
              })
            : [];

        const shareCount = project.uid
            ? await projectsRepository.getShareCount(project.uid)
            : 0;

        return {
            ...projectJson,
            tags: sortTags(projectJson.Tags),
            Tasks: normalizedTasks,
            Notes: normalizedNotes,
            due_date_at: formatDate(project.due_date_at),
            user_id: project.user_id,
            share_count: shareCount,
            is_shared: shareCount > 0,
        };
    }

    /**
     * Check if project exists and return UID (for authorization).
     */
    async getProjectUidIfExists(uidOrSlug) {
        const uid = extractUidFromSlug(uidOrSlug);
        const project = await projectsRepository.findByUid(uid);
        return project ? project.uid : null;
    }

    /**
     * Create a new project.
     */
    async create(userId, data) {
        const {
            name,
            description,
            area_id,
            priority,
            due_date_at,
            image_url,
            status,
            state,
            tags,
            Tags,
        } = data;

        const validatedName = validateName(name);
        const tagsData = tags || Tags;
        const projectUid = generateUid();

        const projectData = {
            uid: projectUid,
            name: validatedName,
            description: description || '',
            area_id: area_id || null,
            pin_to_sidebar: false,
            priority: priority || null,
            due_date_at: due_date_at || null,
            image_url: image_url || null,
            status: status || state || 'not_started',
            user_id: userId,
        };

        const project = await projectsRepository.create(projectData);

        try {
            await updateProjectTags(project, tagsData, userId);
        } catch (tagError) {
            logError(
                'Tag update failed, but project created successfully:',
                tagError.message
            );
        }

        return {
            ...project.toJSON(),
            uid: projectUid,
            tags: [],
            due_date_at: formatDate(project.due_date_at),
        };
    }

    /**
     * Update a project.
     */
    async update(userId, uid, data) {
        const validatedUid = validateUid(uid);
        const project = await projectsRepository.findOne({ uid: validatedUid });

        if (!project) {
            throw new NotFoundError('Project not found.');
        }

        const {
            name,
            description,
            area_id,
            pin_to_sidebar,
            priority,
            due_date_at,
            image_url,
            status,
            state,
            tags,
            Tags,
        } = data;

        const tagsData = tags || Tags;
        const updateData = {};

        if (name !== undefined) updateData.name = name;
        if (description !== undefined) updateData.description = description;
        if (area_id !== undefined)
            updateData.area_id = area_id === '' ? null : area_id;
        if (pin_to_sidebar !== undefined)
            updateData.pin_to_sidebar = pin_to_sidebar;
        if (priority !== undefined)
            updateData.priority = priority === '' ? null : priority;
        if (due_date_at !== undefined)
            updateData.due_date_at = due_date_at === '' ? null : due_date_at;
        if (image_url !== undefined)
            updateData.image_url = image_url === '' ? null : image_url;
        if (status !== undefined) updateData.status = status;
        else if (state !== undefined) updateData.status = state;

        await projectsRepository.update(project, updateData);
        await updateProjectTags(project, tagsData, userId);

        const projectWithAssociations =
            await projectsRepository.findByUidWithTagsAndArea(validatedUid);
        const projectJson = projectWithAssociations.toJSON();

        return {
            ...projectJson,
            tags: sortTags(projectJson.Tags),
            due_date_at: formatDate(projectWithAssociations.due_date_at),
        };
    }

    /**
     * Delete a project.
     */
    async delete(userId, uid) {
        const validatedUid = validateUid(uid);
        const project = await projectsRepository.findOne({ uid: validatedUid });

        if (!project) {
            throw new NotFoundError('Project not found.');
        }

        await projectsRepository.deleteWithOrphaning(project, userId);
        return { message: 'Project successfully deleted' };
    }
}

module.exports = new ProjectsService();
module.exports.updateProjectTags = updateProjectTags;
