// services/project.ts
// Copyright (C) 2026 Robert Colbert <rob.colbert@openplatform.us>
// All Rights Reserved

import slug from "slug";

import { MongooseBaseQueryOptions, PopulateOptions } from "mongoose";

import { IProject, IUser, ProjectStatus } from "@gadget/api";
import Project from "@/models/project.js";

import { DtpService } from "../lib/service.js";

export interface IProjectDefinition {
  name: string;
  slug: string;
  gitUrl?: string;
  status?: ProjectStatus;
}

class ProjectService extends DtpService {
  private populateProject: PopulateOptions[];

  get name(): string {
    return "ProjectService";
  }

  get slug(): string {
    return "svc:project";
  }

  constructor() {
    super();
    this.populateProject = [
      {
        path: "user",
        select: "-passwordSalt -password",
      },
    ];
  }

  async start(): Promise<void> {
    this.log.info("service started");
  }

  async stop(): Promise<void> {
    this.log.info("service stopped");
  }

  async create(user: IUser, definition: IProjectDefinition): Promise<IProject> {
    const NOW = new Date();

    const project = new Project();
    project.createdAt = NOW;
    project.user = user._id;
    project.status = ProjectStatus.Active;
    project.name = definition.name;
    project.slug = slug(definition.slug.trim().toLowerCase());

    if (definition.gitUrl) {
      project.gitUrl = definition.gitUrl;
    }

    this.log.info("creating project", { name: project.name });
    await project.save();

    return project.populate(this.populateProject);
  }

  async update(
    project: IProject,
    definition: IProjectDefinition,
  ): Promise<IProject> {
    const update: MongooseBaseQueryOptions = { $set: {}, $unset: {} };

    if (definition.status) {
      if (definition.status !== project.status) {
        update.$set.status = definition.status;
      }
    }

    if (definition.name) {
      if (definition.name !== project.name) {
        update.$set.name = definition.name;
      }
    }

    if (definition.slug) {
      if (definition.slug !== project.slug) {
        update.$set.slug = slug(definition.slug.trim().toLowerCase());
      }
    }

    if (definition.gitUrl) {
      if (definition.gitUrl !== project.gitUrl) {
        update.$set.gitUrl = definition.gitUrl;
      }
    } else {
      update.$unset.gitUrl = 1;
    }

    const newProject = await Project.findOneAndUpdate(
      { _id: project._id },
      update,
      { new: true, populate: this.populateProject },
    );
    if (!newProject) {
      const error = new Error("project has been removed");
      error.statusCode = 404;
      throw error;
    }

    this.log.info("project updated", {
      old: project,
      new: newProject.toObject(),
    });

    return newProject;
  }

  async setStatus(project: IProject, status: ProjectStatus): Promise<IProject> {
    const newProject = await Project.findOneAndUpdate(
      { _id: project._id },
      { $set: { status } },
      { new: true, populate: this.populateProject },
    );
    if (!newProject) {
      const error = new Error("project has been removed");
      error.statusCode = 404;
      throw error;
    }

    this.log.info("project status updated", {
      project: {
        _id: project._id,
        slug: project.slug,
      },
      old: project.status,
      new: newProject.status,
    });

    return newProject;
  }

  async getForUser(user: IUser): Promise<IProject[]> {
    const projects = await Project.find({ user: user._id })
      .sort({ name: 1 })
      .populate(this.populateProject);
    return projects;
  }

  async findById(id: string): Promise<IProject | null> {
    return Project.findById(id).populate(this.populateProject);
  }

  async findBySlug(slug: string, user: IUser): Promise<IProject | null> {
    return Project.findOne({ slug, user: user._id }).populate(
      this.populateProject,
    );
  }

  async delete(project: IProject): Promise<void> {
    await Project.deleteOne({ _id: project._id });
  }
}

export default new ProjectService();
