import { db } from '../config/database.js';
import { projects, todos, notes, timesheets, companies, projectUsers, users } from '../db/schema.js';
import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm';
import { NotFoundError, ConflictError } from '../utils/errors.js';
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
import {
  logProjectCreated, logProjectDeleted, logProjectUpdated,
  logProjectUserAssigned, logProjectUserRemoved,
} from './audit.service.js';

/**
 * Get all projects
 * Optionally filter by search term or company
 * Returns projects with creator info
 * For members: returns only projects they are directly assigned to
 */
export const getAllProjects = async (searchTerm = null, companyId = null, userId = null, userRole = null) => {
  // Pre membera ziskaj pristupne project IDs
  let accessibleProjectIds = null;

  if (userRole && userRole !== 'admin' && userId) {
    // Ziskaj projekty kde je user priamo priradeny
    accessibleProjectIds = await getAccessibleResourceIds('project', userId);

    // Ak member nema pristup k ziadnym projektom, vrat prazdne pole
    if (accessibleProjectIds.length === 0) {
      return [];
    }
  }

  let query = db
    .select({
      id: projects.id,
      name: projects.name,
      description: projects.description,
      companyId: projects.companyId,
      status: projects.status,
      startDate: projects.startDate,
      endDate: projects.endDate,
      createdBy: projects.createdBy,
      createdAt: projects.createdAt,
      updatedAt: projects.updatedAt,
      creator: {
        id: users.id,
        username: users.username,
      },
    })
    .from(projects)
    .leftJoin(users, eq(projects.createdBy, users.id));

  const conditions = [];

  // Filtrovanie pre membera - projekt je pristupny iba ak je user priamo priradeny
  if (accessibleProjectIds !== null) {
    conditions.push(inArray(projects.id, accessibleProjectIds));
  }

  if (searchTerm) {
    conditions.push(
      or(
        ilike(projects.name, `%${searchTerm}%`),
        ilike(projects.description, `%${searchTerm}%`)
      )
    );
  }

  if (companyId) {
    conditions.push(eq(projects.companyId, companyId));
  }

  if (conditions.length > 0) {
    query = query.where(and(...conditions));
  }

  const result = await query.orderBy(desc(projects.createdAt));
  return result;
};

/**
 * Get project by ID
 * Returns project with creator info
 */
export const getProjectById = async (projectId) => {
  const [project] = await db
    .select({
      id: projects.id,
      name: projects.name,
      description: projects.description,
      companyId: projects.companyId,
      status: projects.status,
      startDate: projects.startDate,
      endDate: projects.endDate,
      createdBy: projects.createdBy,
      createdAt: projects.createdAt,
      updatedAt: projects.updatedAt,
      creator: {
        id: users.id,
        username: users.username,
      },
    })
    .from(projects)
    .leftJoin(users, eq(projects.createdBy, users.id))
    .where(eq(projects.id, projectId))
    .limit(1);

  if (!project) {
    throw new NotFoundError('Projekt nenájdený');
  }

  return project;
};

/**
 * Create new project
 */
export const createProject = async (userId, data, auditContext = null) => {
  const { name, description, companyId, status, startDate, endDate } = data;

  // If companyId is provided, verify company exists
  if (companyId) {
    const [company] = await db
      .select()
      .from(companies)
      .where(eq(companies.id, companyId))
      .limit(1);

    if (!company) {
      throw new NotFoundError('Firma nenájdená');
    }
  }

  const [newProject] = await db
    .insert(projects)
    .values({
      name,
      description: description || null,
      companyId: companyId || null,
      status: status || 'active',
      startDate: startDate ? new Date(startDate) : null,
      endDate: endDate ? new Date(endDate) : null,
      createdBy: userId,
    })
    .returning();

  // Auto-assign the creator to the project so they can access it
  await db.insert(projectUsers).values({
    projectId: newProject.id,
    userId: userId,
    role: 'creator',
    addedBy: userId,
  });

  if (auditContext) {
    await logProjectCreated(auditContext.userId, newProject.id, newProject.name, auditContext.ipAddress, auditContext.userAgent);
  }

  return newProject;
};

/**
 * Update project
 */
export const updateProject = async (projectId, data, auditContext = null) => {
  const project = await getProjectById(projectId);

  const { name, description, companyId, status, startDate, endDate } = data;

  // Treat empty string companyId as null (no company)
  const resolvedCompanyId = companyId === '' ? null : companyId;

  // If companyId is being changed, verify new company exists
  if (resolvedCompanyId !== undefined && resolvedCompanyId !== null && resolvedCompanyId !== project.companyId) {
    const [company] = await db
      .select()
      .from(companies)
      .where(eq(companies.id, resolvedCompanyId))
      .limit(1);

    if (!company) {
      throw new NotFoundError('Firma nenájdená');
    }
  }

  const [updated] = await db
    .update(projects)
    .set({
      name: name !== undefined ? name : project.name,
      description: description !== undefined ? description : project.description,
      companyId: resolvedCompanyId !== undefined ? resolvedCompanyId : project.companyId,
      status: status !== undefined ? status : project.status,
      startDate: startDate !== undefined ? (startDate ? new Date(startDate) : null) : project.startDate,
      endDate: endDate !== undefined ? (endDate ? new Date(endDate) : null) : project.endDate,
      updatedAt: new Date(),
    })
    .where(eq(projects.id, projectId))
    .returning();

  if (auditContext) {
    await logProjectUpdated(auditContext.userId, projectId, { name: project.name }, { name: updated.name }, auditContext.ipAddress, auditContext.userAgent);
  }

  return updated;
};

/**
 * Delete project
 */
export const deleteProject = async (projectId, auditContext = null) => {
  const project = await getProjectById(projectId); // Check if exists

  await db.delete(projects).where(eq(projects.id, projectId));

  if (auditContext) {
    await logProjectDeleted(auditContext.userId, projectId, project.name, auditContext.ipAddress, auditContext.userAgent);
  }

  return { success: true, message: 'Projekt bol odstránený' };
};

/**
 * Get project with related data (todos, notes, timesheets)
 */
export const getProjectWithRelations = async (projectId) => {
  const project = await getProjectById(projectId);

  // Get company if exists
  let company = null;
  if (project.companyId) {
    [company] = await db
      .select()
      .from(companies)
      .where(eq(companies.id, project.companyId))
      .limit(1);
  }

  // Get related todos
  const projectTodos = await db
    .select()
    .from(todos)
    .where(eq(todos.projectId, projectId))
    .orderBy(desc(todos.createdAt));

  // Get related notes
  const projectNotes = await db
    .select()
    .from(notes)
    .where(eq(notes.projectId, projectId))
    .orderBy(desc(notes.createdAt));

  // Get related timesheets
  const projectTimesheets = await db
    .select()
    .from(timesheets)
    .where(eq(timesheets.projectId, projectId))
    .orderBy(desc(timesheets.uploadedAt));

  // Get assigned users (team members)
  const rawUsers = await db
    .select()
    .from(projectUsers)
    .leftJoin(users, eq(projectUsers.userId, users.id))
    .where(eq(projectUsers.projectId, projectId))
    .orderBy(desc(projectUsers.addedAt));

  const assignedUsers = rawUsers.map((row) => ({
    id: row.project_users.id,
    userId: row.project_users.userId,
    role: row.project_users.role,
    addedBy: row.project_users.addedBy,
    addedAt: row.project_users.addedAt,
    user: row.users ? {
      id: row.users.id,
      username: row.users.username,
      email: row.users.email,
      role: row.users.role,
    } : null,
  }));

  return {
    ...project,
    company,
    todos: projectTodos,
    notes: projectNotes,
    timesheets: projectTimesheets,
    assignedUsers,
  };
};

/**
 * Get projects by company ID
 */
export const getProjectsByCompanyId = async (companyId) => {
  return await db
    .select()
    .from(projects)
    .where(eq(projects.companyId, companyId))
    .orderBy(desc(projects.createdAt));
};

/**
 * Get project users (team members)
 */
export const getProjectUsers = async (projectId) => {
  await getProjectById(projectId); // Verify project exists

  const rawResults = await db
    .select()
    .from(projectUsers)
    .leftJoin(users, eq(projectUsers.userId, users.id))
    .where(eq(projectUsers.projectId, projectId))
    .orderBy(desc(projectUsers.addedAt));

  const assignedUsers = rawResults.map((row) => ({
    id: row.project_users.id,
    userId: row.project_users.userId,
    role: row.project_users.role,
    addedBy: row.project_users.addedBy,
    addedAt: row.project_users.addedAt,
    user: row.users ? {
      id: row.users.id,
      username: row.users.username,
      email: row.users.email,
      role: row.users.role,
    } : null,
  }));

  return assignedUsers;
};

/**
 * Assign user to project
 */
export const assignUserToProject = async (projectId, userId, addedByUserId, role = null, auditContext = null) => {
  const project = await getProjectById(projectId); // Verify project exists

  // Verify user exists
  const [user] = await db
    .select()
    .from(users)
    .where(eq(users.id, userId))
    .limit(1);

  if (!user) {
    throw new NotFoundError('Používateľ nenájdený');
  }

  // Check if user is already assigned
  const [existing] = await db
    .select()
    .from(projectUsers)
    .where(and(eq(projectUsers.projectId, projectId), eq(projectUsers.userId, userId)))
    .limit(1);

  if (existing) {
    throw new ConflictError('Používateľ je už priradený k projektu');
  }

  // Assign user to project
  const [assignment] = await db
    .insert(projectUsers)
    .values({
      projectId,
      userId,
      role: role || null,
      addedBy: addedByUserId,
    })
    .returning();

  // Return with user details
  const [row] = await db
    .select()
    .from(projectUsers)
    .leftJoin(users, eq(projectUsers.userId, users.id))
    .where(eq(projectUsers.id, assignment.id))
    .limit(1);

  if (auditContext) {
    await logProjectUserAssigned(auditContext.userId, projectId, userId, project.name, auditContext.ipAddress, auditContext.userAgent);
  }

  return {
    id: row.project_users.id,
    userId: row.project_users.userId,
    role: row.project_users.role,
    addedBy: row.project_users.addedBy,
    addedAt: row.project_users.addedAt,
    user: row.users ? {
      id: row.users.id,
      username: row.users.username,
      email: row.users.email,
      role: row.users.role,
    } : null,
  };
};

/**
 * Remove user from project
 */
export const removeUserFromProject = async (projectId, userId, auditContext = null) => {
  const project = await getProjectById(projectId); // Verify project exists

  // Check if user is assigned
  const [existing] = await db
    .select()
    .from(projectUsers)
    .where(and(eq(projectUsers.projectId, projectId), eq(projectUsers.userId, userId)))
    .limit(1);

  if (!existing) {
    throw new NotFoundError('Používateľ nie je priradený k projektu');
  }

  // Remove assignment
  await db
    .delete(projectUsers)
    .where(and(eq(projectUsers.projectId, projectId), eq(projectUsers.userId, userId)));

  if (auditContext) {
    await logProjectUserRemoved(auditContext.userId, projectId, userId, project.name, auditContext.ipAddress, auditContext.userAgent);
  }

  return { success: true, message: 'Používateľ bol odstránený z projektu' };
};

/**
 * Update user role on project
 */
export const updateUserRoleOnProject = async (projectId, userId, role) => {
  await getProjectById(projectId); // Verify project exists

  // Check if user is assigned
  const [existing] = await db
    .select()
    .from(projectUsers)
    .where(and(eq(projectUsers.projectId, projectId), eq(projectUsers.userId, userId)))
    .limit(1);

  if (!existing) {
    throw new NotFoundError('Používateľ nie je priradený k projektu');
  }

  // Update role
  const [updated] = await db
    .update(projectUsers)
    .set({ role: role || null })
    .where(and(eq(projectUsers.projectId, projectId), eq(projectUsers.userId, userId)))
    .returning();

  // Return with user details
  const [row] = await db
    .select()
    .from(projectUsers)
    .leftJoin(users, eq(projectUsers.userId, users.id))
    .where(eq(projectUsers.id, updated.id))
    .limit(1);

  return {
    id: row.project_users.id,
    userId: row.project_users.userId,
    role: row.project_users.role,
    addedBy: row.project_users.addedBy,
    addedAt: row.project_users.addedAt,
    user: row.users ? {
      id: row.users.id,
      username: row.users.username,
      email: row.users.email,
      role: row.users.role,
    } : null,
  };
};
