import fs from 'node:fs/promises';
import path from 'node:path';
import type { Request, Response, NextFunction } from 'express';
import * as projectModel from '../models/project.model.js';
import { successResponse, errorResponse } from '../utils/response.js';
import { parsePagination, paginatedResponse } from '../utils/pagination.js';
import { UPLOAD_ROOT } from '../middlewares/upload.js';
import logger from '../config/logger.js';

function actorId(req: Request): number | undefined {
  return req.user ? Number(req.user.sub) : undefined;
}

async function createProject(req: Request, res: Response, next: NextFunction) {
  try {
    const project = await projectModel.createProject(req.body);
    await projectModel.logActivity(project.id, actorId(req), 'Project created');
    return successResponse(res, 'Project created successfully', project, 201);
  } catch (error) {
    next(error);
    return;
  }
}

async function getProjects(req: Request, res: Response, next: NextFunction) {
  try {
    const pagination = parsePagination(req.query);
    const [projects, total] = await Promise.all([
      projectModel.getProjects(pagination),
      projectModel.countProjects(),
    ]);
    return successResponse(res, 'Projects fetched successfully', paginatedResponse(projects, total, pagination));
  } catch (error) {
    next(error);
    return;
  }
}

async function getProject(req: Request, res: Response, next: NextFunction) {
  try {
    const project = await projectModel.getProjectById(Number(req.params.id));
    if (!project) return void errorResponse(res, 'Project not found', 404);
    return successResponse(res, 'Project fetched successfully', project);
  } catch (error) {
    next(error);
    return;
  }
}

async function updateProject(req: Request, res: Response, next: NextFunction) {
  try {
    const id = Number(req.params.id);
    const project = await projectModel.updateProject(id, req.body);
    const changedFields = Object.keys(req.body).join(', ');
    await projectModel.logActivity(id, actorId(req), 'Project details updated', changedFields || undefined);
    return successResponse(res, 'Project updated successfully', project);
  } catch (error) {
    next(error);
    return;
  }
}

async function deleteProject(req: Request, res: Response, next: NextFunction) {
  try {
    const id = Number(req.params.id);
    const documents = await projectModel.getDocuments(id);
    await projectModel.deleteProject(id);
    await Promise.all(
      documents.map((document) =>
        fs.unlink(path.join(UPLOAD_ROOT, 'projects', document.filePath)).catch((error: unknown) => {
          logger.error('Failed to remove project document file after project delete', {
            filePath: document.filePath,
            message: error instanceof Error ? error.message : String(error),
          });
        }),
      ),
    );
    return successResponse(res, 'Project deleted successfully');
  } catch (error) {
    next(error);
    return;
  }
}

async function submitProject(req: Request, res: Response, next: NextFunction) {
  try {
    const id = Number(req.params.id);
    const status = await projectModel.findProjectStatus(id);
    if (!status) return void errorResponse(res, 'Project not found', 404);
    if (status !== 'draft') return void errorResponse(res, `Cannot submit a project with status "${status}"`, 409);
    const project = await projectModel.setProjectStatus(id, 'submitted');
    await projectModel.logActivity(id, actorId(req), 'Status changed to Submitted');
    return successResponse(res, 'Project submitted for review', project);
  } catch (error) {
    next(error);
    return;
  }
}

async function approveProject(req: Request, res: Response, next: NextFunction) {
  try {
    const id = Number(req.params.id);
    const status = await projectModel.findProjectStatus(id);
    if (!status) return void errorResponse(res, 'Project not found', 404);
    if (status !== 'submitted') return void errorResponse(res, `Cannot approve a project with status "${status}"`, 409);
    const project = await projectModel.setProjectStatus(id, 'active');
    await projectModel.logActivity(id, actorId(req), 'Status changed to Active');
    return successResponse(res, 'Project approved and activated', project);
  } catch (error) {
    next(error);
    return;
  }
}

async function getActivityLog(req: Request, res: Response, next: NextFunction) {
  try {
    const projectId = Number(req.params.id);
    const pagination = parsePagination(req.query);
    const [entries, total] = await Promise.all([
      projectModel.getActivityLog(projectId, pagination),
      projectModel.countActivityLog(projectId),
    ]);
    return successResponse(res, 'Activity log fetched successfully', paginatedResponse(entries, total, pagination));
  } catch (error) {
    next(error);
    return;
  }
}

async function getTeamMembers(req: Request, res: Response, next: NextFunction) {
  try {
    const members = await projectModel.getTeamMembers(Number(req.params.id));
    return successResponse(res, 'Project team fetched successfully', members);
  } catch (error) {
    next(error);
    return;
  }
}

async function addTeamMember(req: Request, res: Response, next: NextFunction) {
  try {
    const projectId = Number(req.params.id);
    const member = await projectModel.addTeamMember(projectId, req.body);
    await projectModel.logActivity(projectId, actorId(req), `${member.user.name} added to team as ${member.roleOnProject}`);
    return successResponse(res, 'Team member added successfully', member, 201);
  } catch (error) {
    next(error);
    return;
  }
}

async function removeTeamMember(req: Request, res: Response, next: NextFunction) {
  try {
    const projectId = Number(req.params.id);
    const memberId = Number(req.params.memberId);
    const member = await projectModel.removeTeamMember(projectId, memberId);
    if (!member) return void errorResponse(res, 'Team member not found on this project', 404);
    await projectModel.logActivity(projectId, actorId(req), `${member.user.name} removed from team`);
    return successResponse(res, 'Team member removed successfully');
  } catch (error) {
    next(error);
    return;
  }
}

async function getDocuments(req: Request, res: Response, next: NextFunction) {
  try {
    const documents = await projectModel.getDocuments(Number(req.params.id));
    return successResponse(res, 'Documents fetched successfully', documents);
  } catch (error) {
    next(error);
    return;
  }
}

async function uploadDocument(req: Request, res: Response, next: NextFunction) {
  try {
    if (!req.file) return void errorResponse(res, 'A file is required', 400);
    const projectId = Number(req.params.id);
    const document = await projectModel.createDocument(
      projectId,
      {
        fileName: req.file.originalname,
        filePath: req.file.filename,
        fileSize: req.file.size,
        mimeType: req.file.mimetype,
      },
      actorId(req),
    );
    await projectModel.logActivity(projectId, actorId(req), `${req.file.originalname} uploaded`);
    return successResponse(res, 'Document uploaded successfully', document, 201);
  } catch (error) {
    next(error);
    return;
  }
}

async function downloadDocument(req: Request, res: Response, next: NextFunction) {
  try {
    const projectId = Number(req.params.id);
    const documentId = Number(req.params.documentId);
    const document = await projectModel.getDocumentById(projectId, documentId);
    if (!document) return void errorResponse(res, 'Document not found', 404);
    res.download(path.join(UPLOAD_ROOT, 'projects', document.filePath), document.fileName);
  } catch (error) {
    next(error);
    return;
  }
}

async function deleteDocument(req: Request, res: Response, next: NextFunction) {
  try {
    const projectId = Number(req.params.id);
    const documentId = Number(req.params.documentId);
    const document = await projectModel.deleteDocument(projectId, documentId);
    if (!document) return void errorResponse(res, 'Document not found', 404);
    await fs.unlink(path.join(UPLOAD_ROOT, 'projects', document.filePath)).catch((error: unknown) => {
      logger.error('Failed to remove project document file', {
        filePath: document.filePath,
        message: error instanceof Error ? error.message : String(error),
      });
    });
    await projectModel.logActivity(projectId, actorId(req), `${document.fileName} deleted`);
    return successResponse(res, 'Document deleted successfully');
  } catch (error) {
    next(error);
    return;
  }
}

export {
  createProject,
  getProjects,
  getProject,
  updateProject,
  deleteProject,
  submitProject,
  approveProject,
  getActivityLog,
  getTeamMembers,
  addTeamMember,
  removeTeamMember,
  getDocuments,
  uploadDocument,
  downloadDocument,
  deleteDocument,
};
