import { db } from '../config/database.js';
import { companies, projects, todos, notes, companyReminders, companyUsers, users, personalContacts } from '../db/schema.js';
import { eq, desc, ilike, or, and, inArray, sql } from 'drizzle-orm';
import { NotFoundError, ConflictError } from '../utils/errors.js';
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
import {
  logCompanyCreated, logCompanyDeleted, logCompanyUpdated,
  logCompanyUserAssigned, logCompanyUserRemoved,
} from './audit.service.js';

/**
 * Get all companies
 * Optionally filter by search term
 * Returns companies with creator info
 * For members: returns only companies they are assigned to
 */
export const getAllCompanies = async (searchTerm = null, userId = null, userRole = null) => {
  // Pre membera najprv ziskaj pristupne company IDs
  let accessibleCompanyIds = null;
  if (userRole && userRole !== 'admin' && userId) {
    accessibleCompanyIds = await getAccessibleResourceIds('company', userId);
    // Ak member nema pristup k ziadnym firmam, vrat prazdne pole
    if (accessibleCompanyIds.length === 0) {
      return [];
    }
  }

  let query = db
    .select({
      id: companies.id,
      name: companies.name,
      description: companies.description,
      address: companies.address,
      city: companies.city,
      postalCode: companies.postalCode,
      country: companies.country,
      phone: companies.phone,
      email: companies.email,
      website: companies.website,
      status: companies.status,
      createdBy: companies.createdBy,
      createdAt: companies.createdAt,
      updatedAt: companies.updatedAt,
      creator: {
        id: users.id,
        username: users.username,
      },
    })
    .from(companies)
    .leftJoin(users, eq(companies.createdBy, users.id));

  // Aplikuj filtrovanie pre membera
  const conditions = [];

  if (accessibleCompanyIds !== null) {
    conditions.push(inArray(companies.id, accessibleCompanyIds));
  }

  if (searchTerm) {
    // Find company IDs that have matching contacts
    const contactMatches = await db
      .selectDistinct({ companyId: personalContacts.companyId })
      .from(personalContacts)
      .where(
        and(
          sql`${personalContacts.companyId} IS NOT NULL`,
          or(
            ilike(personalContacts.firstName, `%${searchTerm}%`),
            ilike(personalContacts.lastName, `%${searchTerm}%`),
            ilike(personalContacts.email, `%${searchTerm}%`),
            ilike(personalContacts.phone, `%${searchTerm}%`)
          )
        )
      );

    const contactCompanyIds = contactMatches.map((c) => c.companyId).filter(Boolean);

    conditions.push(
      or(
        ilike(companies.name, `%${searchTerm}%`),
        ilike(companies.email, `%${searchTerm}%`),
        ilike(companies.city, `%${searchTerm}%`),
        ilike(companies.phone, `%${searchTerm}%`),
        contactCompanyIds.length > 0 ? inArray(companies.id, contactCompanyIds) : sql`false`
      )
    );
  }

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

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

/**
 * Get company by ID
 * Returns company with creator info
 */
export const getCompanyById = async (companyId) => {
  const [company] = await db
    .select({
      id: companies.id,
      name: companies.name,
      description: companies.description,
      address: companies.address,
      city: companies.city,
      postalCode: companies.postalCode,
      country: companies.country,
      phone: companies.phone,
      email: companies.email,
      website: companies.website,
      status: companies.status,
      createdBy: companies.createdBy,
      createdAt: companies.createdAt,
      updatedAt: companies.updatedAt,
      creator: {
        id: users.id,
        username: users.username,
      },
    })
    .from(companies)
    .leftJoin(users, eq(companies.createdBy, users.id))
    .where(eq(companies.id, companyId))
    .limit(1);

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

  return company;
};

/**
 * Create new company
 */
export const createCompany = async (userId, data, auditContext = null) => {
  const { name, description, address, city, postalCode, country, phone, email, website, status } = data;

  // Check if company with same name already exists
  const [existing] = await db
    .select()
    .from(companies)
    .where(eq(companies.name, name))
    .limit(1);

  if (existing) {
    throw new ConflictError('Firma s týmto názvom už existuje');
  }

  const [newCompany] = await db
    .insert(companies)
    .values({
      name,
      description: description || null,
      address: address || null,
      city: city || null,
      postalCode: postalCode || null,
      country: country || null,
      phone: phone || null,
      email: email || null,
      website: website || null,
      status: status || 'registered',
      createdBy: userId,
    })
    .returning();

  // Auto-assign the creator to the company so they can access it
  await db.insert(companyUsers).values({
    companyId: newCompany.id,
    userId: userId,
    role: 'creator',
    addedBy: userId,
  });

  if (auditContext) {
    await logCompanyCreated(auditContext.userId, newCompany.id, newCompany.name, auditContext.ipAddress, auditContext.userAgent);
  }

  return newCompany;
};

/**
 * Update company
 */
export const updateCompany = async (companyId, data, auditContext = null) => {
  const company = await getCompanyById(companyId);

  const { name, description, address, city, postalCode, country, phone, email, website, status } = data;

  // If name is being changed, check for duplicates
  if (name && name !== company.name) {
    const [existing] = await db
      .select()
      .from(companies)
      .where(and(eq(companies.name, name), eq(companies.id, companyId)))
      .limit(1);

    if (existing && existing.id !== companyId) {
      throw new ConflictError('Firma s týmto názvom už existuje');
    }
  }

  const [updated] = await db
    .update(companies)
    .set({
      name: name !== undefined ? name : company.name,
      description: description !== undefined ? description : company.description,
      address: address !== undefined ? address : company.address,
      city: city !== undefined ? city : company.city,
      postalCode: postalCode !== undefined ? postalCode : company.postalCode,
      country: country !== undefined ? country : company.country,
      phone: phone !== undefined ? phone : company.phone,
      email: email !== undefined ? email : company.email,
      website: website !== undefined ? website : company.website,
      status: status !== undefined ? status : company.status,
      updatedAt: new Date(),
    })
    .where(eq(companies.id, companyId))
    .returning();

  if (auditContext) {
    await logCompanyUpdated(auditContext.userId, companyId, { name: company.name }, { name: updated.name }, auditContext.ipAddress, auditContext.userAgent);
  }

  return updated;
};

/**
 * Delete company
 */
export const deleteCompany = async (companyId, auditContext = null) => {
  const company = await getCompanyById(companyId); // Check if exists

  await db.delete(companies).where(eq(companies.id, companyId));

  if (auditContext) {
    await logCompanyDeleted(auditContext.userId, companyId, company.name, auditContext.ipAddress, auditContext.userAgent);
  }

  return { success: true, message: 'Firma bola odstránená' };
};

/**
 * Get company with related data (projects, todos, notes)
 */
export const getCompanyWithRelations = async (companyId) => {
  const company = await getCompanyById(companyId);

  // Get related projects
  const companyProjects = await db
    .select()
    .from(projects)
    .where(eq(projects.companyId, companyId))
    .orderBy(desc(projects.createdAt));

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

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

  const companyReminderList = await db
    .select()
    .from(companyReminders)
    .where(eq(companyReminders.companyId, companyId))
    .orderBy(desc(companyReminders.createdAt));

  return {
    ...company,
    projects: companyProjects,
    todos: companyTodos,
    notes: companyNotes,
    reminders: companyReminderList,
  };
};

/**
 * Get all users assigned to a company
 */
export const getCompanyUsers = async (companyId) => {
  await getCompanyById(companyId); // Verify company exists

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

  const assignedUsers = rawResults.map((row) => ({
    id: row.company_users.id,
    userId: row.company_users.userId,
    role: row.company_users.role,
    addedBy: row.company_users.addedBy,
    addedAt: row.company_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 company
 */
export const assignUserToCompany = async (companyId, userId, addedByUserId, role = null, auditContext = null) => {
  const company = await getCompanyById(companyId); // Verify company 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(companyUsers)
    .where(and(eq(companyUsers.companyId, companyId), eq(companyUsers.userId, userId)))
    .limit(1);

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

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

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

  if (auditContext) {
    await logCompanyUserAssigned(auditContext.userId, companyId, userId, company.name, auditContext.ipAddress, auditContext.userAgent);
  }

  return {
    id: row.company_users.id,
    userId: row.company_users.userId,
    role: row.company_users.role,
    addedBy: row.company_users.addedBy,
    addedAt: row.company_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 company
 */
export const removeUserFromCompany = async (companyId, userId, auditContext = null) => {
  const company = await getCompanyById(companyId); // Verify company exists

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

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

  // Remove assignment
  await db
    .delete(companyUsers)
    .where(and(eq(companyUsers.companyId, companyId), eq(companyUsers.userId, userId)));

  if (auditContext) {
    await logCompanyUserRemoved(auditContext.userId, companyId, userId, company.name, auditContext.ipAddress, auditContext.userAgent);
  }

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

/**
 * Update user role on company
 */
export const updateUserRoleOnCompany = async (companyId, userId, role) => {
  await getCompanyById(companyId); // Verify company exists

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

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

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

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

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