// 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, IProjectSkill, IProjectTask, 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;
  description?: string;
  system?: string;
  skills?: IProjectSkill[];
  tasks?: IProjectTask[];
}

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");
  }

  /**
   * Parse a git URL to extract the repository name.
   * Handles common formats:
   *   https://github.com/user/repo.git
   *   https://github.com/user/repo
   *   git@github.com:user/repo.git
   *   ssh://git@github.com/user/repo.git
   */
  parseGitUrl(gitUrl: string): { name: string; slug: string } {
    const url = gitUrl.trim();
    let repoName: string;

    // SSH format: git@host:owner/repo.git
    if (url.includes(":") && !url.startsWith("http") && !url.startsWith("ssh://")) {
      const parts = url.split(":").pop()!;
      repoName = parts.split("/").pop()!;
    } else {
      // HTTP(S) or ssh:// format
      const parsed = new URL(url);
      const segments = parsed.pathname.split("/").filter(Boolean);
      repoName = segments[segments.length - 1] || "";
    }

    // Strip .git suffix
    repoName = repoName.replace(/\.git$/, "");

    return {
      name: repoName,
      slug: slug(repoName),
    };
  }

  /**
   * Create a project from a git URL by parsing the URL to derive
   * the project name and slug automatically. Handles slug collisions
   * by appending a numeric suffix (e.g., my-repo-2).
   */
  async createFromGitUrl(user: IUser, gitUrl: string): Promise<IProject> {
    const { name, slug: baseSlug } = this.parseGitUrl(gitUrl);

    if (!name) {
      const error = new Error("could not derive project name from git URL");
      error.statusCode = 400;
      throw error;
    }

    // Resolve slug collisions by appending a numeric suffix
    let projectSlug = baseSlug;
    let suffix = 2;
    while (await Project.findOne({ slug: projectSlug, user: user._id })) {
      projectSlug = `${baseSlug}-${suffix}`;
      suffix++;
    }

    this.log.info("creating project from git URL", { name, slug: projectSlug, gitUrl });

    return this.create(user, {
      name,
      slug: projectSlug,
      gitUrl,
    });
  }

  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 if (definition.gitUrl === undefined && definition.hasOwnProperty('gitUrl')) {
      // Only unset if explicitly provided as undefined/empty
    } else {
      update.$unset.gitUrl = 1;
    }

    // Description — set or unset
    if (definition.description !== undefined) {
      if (definition.description !== project.description) {
        update.$set.description = definition.description;
      }
    }

    // System prompt — set or unset
    if (definition.system !== undefined) {
      if (definition.system !== project.system) {
        update.$set.system = definition.system;
      }
    }

    // Skills — full array replacement
    if (definition.skills !== undefined) {
      update.$set.skills = definition.skills;
    }

    // Tasks — full array replacement
    if (definition.tasks !== undefined) {
      update.$set.tasks = definition.tasks;
    }

    // Clean up empty $unset to avoid MongoDB errors
    if (Object.keys(update.$unset as Record<string, unknown>).length === 0) {
      delete (update as any).$unset;
    }

    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();
