/**
 * Mosaic Stack Gantt API Client
 *
 * Provides typed client for querying project timelines, tasks, and dependencies
 * from Mosaic Stack's API.
 *
 * @example
 * ```typescript
 * const client = new GanttClient({
 *   apiUrl: process.env.MOSAIC_API_URL,
 *   workspaceId: process.env.MOSAIC_WORKSPACE_ID,
 *   apiToken: process.env.MOSAIC_API_TOKEN,
 * });
 *
 * const projects = await client.listProjects();
 * const timeline = await client.getProjectTimeline('project-id');
 * const criticalPath = await client.calculateCriticalPath('project-id');
 * ```
 */

export interface GanttClientConfig {
  apiUrl: string;
  workspaceId: string;
  apiToken: string;
}

export type ProjectStatus = "PLANNING" | "ACTIVE" | "ON_HOLD" | "COMPLETED" | "ARCHIVED";
export type TaskStatus = "NOT_STARTED" | "IN_PROGRESS" | "PAUSED" | "COMPLETED" | "ARCHIVED";
export type TaskPriority = "LOW" | "MEDIUM" | "HIGH" | "URGENT";

export interface ProjectMetadata {
  [key: string]: unknown;
}

export interface Project {
  id: string;
  workspaceId: string;
  name: string;
  description: string | null;
  status: ProjectStatus;
  startDate: string | null;
  endDate: string | null;
  color: string | null;
  metadata: ProjectMetadata;
  createdAt: string;
  updatedAt: string;
  tasks?: Task[];
  _count?: {
    tasks: number;
    events: number;
  };
}

export interface TaskMetadata {
  startDate?: string;
  dependencies?: string[];
  [key: string]: unknown;
}

export interface Task {
  id: string;
  workspaceId: string;
  title: string;
  description: string | null;
  status: TaskStatus;
  priority: TaskPriority;
  dueDate: string | null;
  completedAt: string | null;
  projectId: string | null;
  assigneeId: string | null;
  creatorId: string;
  parentId: string | null;
  sortOrder: number;
  metadata: TaskMetadata;
  createdAt: string;
  updatedAt: string;
  project?: {
    id: string;
    name: string;
    color: string | null;
  };
}

export interface PaginatedResponse<T> {
  data: T[];
  meta: {
    total: number;
    page: number;
    limit: number;
    totalPages: number;
  };
}

export interface ProjectTimeline {
  project: Project;
  tasks: Task[];
  stats: {
    total: number;
    completed: number;
    inProgress: number;
    notStarted: number;
    paused: number;
    targetPassed: number;
  };
}

export interface DependencyChain {
  task: Task;
  blockedBy: Task[];
  blocks: Task[];
}

export interface CriticalPath {
  path: Array<{
    task: Task;
    duration: number;
    cumulativeDuration: number;
  }>;
  totalDuration: number;
  nonCriticalTasks: Array<{
    task: Task;
    slack: number;
  }>;
}

export class GanttClient {
  private readonly config: GanttClientConfig;

  constructor(config: GanttClientConfig) {
    this.config = config;
  }

  /**
   * Make an authenticated API request
   */
  private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
    const url = `${this.config.apiUrl}${endpoint}`;
    const headers = {
      "Content-Type": "application/json",
      "X-Workspace-Id": this.config.workspaceId,
      Authorization: `Bearer ${this.config.apiToken}`,
      ...options.headers,
    };

    const response = await fetch(url, {
      ...options,
      headers,
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`API request failed: ${response.status} ${error}`);
    }

    return response.json() as Promise<T>;
  }

  /**
   * List all projects with pagination
   */
  async listProjects(params?: {
    page?: number;
    limit?: number;
    status?: ProjectStatus;
  }): Promise<PaginatedResponse<Project>> {
    const queryParams = new URLSearchParams();
    if (params?.page) queryParams.set("page", params.page.toString());
    if (params?.limit) queryParams.set("limit", params.limit.toString());
    if (params?.status) queryParams.set("status", params.status);

    const query = queryParams.toString();
    const endpoint = `/projects${query ? `?${query}` : ""}`;

    return this.request<PaginatedResponse<Project>>(endpoint);
  }

  /**
   * Get a single project with tasks
   */
  async getProject(projectId: string): Promise<Project> {
    return this.request<Project>(`/projects/${projectId}`);
  }

  /**
   * Get tasks with optional filters
   */
  async getTasks(params?: {
    projectId?: string;
    status?: TaskStatus;
    priority?: TaskPriority;
    assigneeId?: string;
    page?: number;
    limit?: number;
  }): Promise<PaginatedResponse<Task>> {
    const queryParams = new URLSearchParams();
    if (params?.projectId) queryParams.set("projectId", params.projectId);
    if (params?.status) queryParams.set("status", params.status);
    if (params?.priority) queryParams.set("priority", params.priority);
    if (params?.assigneeId) queryParams.set("assigneeId", params.assigneeId);
    if (params?.page) queryParams.set("page", params.page.toString());
    if (params?.limit) queryParams.set("limit", params.limit.toString());

    const query = queryParams.toString();
    const endpoint = `/tasks${query ? `?${query}` : ""}`;

    return this.request<PaginatedResponse<Task>>(endpoint);
  }

  /**
   * Get a single task
   */
  async getTask(taskId: string): Promise<Task> {
    return this.request<Task>(`/tasks/${taskId}`);
  }

  /**
   * Get project timeline with statistics
   */
  async getProjectTimeline(projectId: string): Promise<ProjectTimeline> {
    const project = await this.getProject(projectId);
    const tasksResponse = await this.getTasks({ projectId, limit: 1000 });
    const tasks = tasksResponse.data;

    const now = new Date();
    const stats = {
      total: tasks.length,
      completed: tasks.filter((t) => t.status === "COMPLETED").length,
      inProgress: tasks.filter((t) => t.status === "IN_PROGRESS").length,
      notStarted: tasks.filter((t) => t.status === "NOT_STARTED").length,
      paused: tasks.filter((t) => t.status === "PAUSED").length,
      targetPassed: tasks.filter((t) => {
        if (!t.dueDate || t.status === "COMPLETED") return false;
        return new Date(t.dueDate) < now;
      }).length,
    };

    return { project, tasks, stats };
  }

  /**
   * Get dependency chain for a task
   */
  async getDependencyChain(taskId: string): Promise<DependencyChain> {
    const task = await this.getTask(taskId);
    const dependencyIds = task.metadata.dependencies ?? [];

    // Fetch tasks this task depends on (blocking tasks)
    const blockedBy = await Promise.all(dependencyIds.map((id) => this.getTask(id)));

    // Find tasks that depend on this task
    const allTasksResponse = await this.getTasks({
      projectId: task.projectId ?? undefined,
      limit: 1000,
    });
    const blocks = allTasksResponse.data.filter((t) => t.metadata.dependencies?.includes(taskId));

    return { task, blockedBy, blocks };
  }

  /**
   * Calculate critical path for a project
   *
   * Uses the Critical Path Method (CPM) to find the longest dependency chain
   */
  async calculateCriticalPath(projectId: string): Promise<CriticalPath> {
    const tasksResponse = await this.getTasks({ projectId, limit: 1000 });
    const tasks = tasksResponse.data;

    // Build dependency graph
    const taskMap = new Map(tasks.map((t) => [t.id, t]));
    const durations = new Map<string, number>();
    const earliestStart = new Map<string, number>();
    const latestStart = new Map<string, number>();

    // Calculate task durations (days between start and due date, or default to 1)
    for (const task of tasks) {
      const start = task.metadata.startDate
        ? new Date(task.metadata.startDate)
        : new Date(task.createdAt);
      const end = task.dueDate ? new Date(task.dueDate) : new Date();
      const duration = Math.max(
        1,
        Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24))
      );
      durations.set(task.id, duration);
    }

    // Forward pass: calculate earliest start times
    const visited = new Set<string>();
    const calculateEarliestStart = (taskId: string): number => {
      if (visited.has(taskId)) {
        return earliestStart.get(taskId) ?? 0;
      }
      visited.add(taskId);

      const task = taskMap.get(taskId);
      if (!task) return 0;

      const deps = task.metadata.dependencies ?? [];
      let maxEnd = 0;

      for (const depId of deps) {
        const depStart = calculateEarliestStart(depId);
        const depDuration = durations.get(depId) ?? 1;
        maxEnd = Math.max(maxEnd, depStart + depDuration);
      }

      earliestStart.set(taskId, maxEnd);
      return maxEnd;
    };

    // Calculate earliest start for all tasks
    for (const task of tasks) {
      calculateEarliestStart(task.id);
    }

    // Find project completion time (max earliest finish)
    let projectDuration = 0;
    for (const task of tasks) {
      const start = earliestStart.get(task.id) ?? 0;
      const duration = durations.get(task.id) ?? 1;
      projectDuration = Math.max(projectDuration, start + duration);
    }

    // Backward pass: calculate latest start times
    const calculateLatestStart = (taskId: string): number => {
      const task = taskMap.get(taskId);
      if (!task) return projectDuration;

      // Find all tasks that depend on this task
      const dependents = tasks.filter((t) => t.metadata.dependencies?.includes(taskId));

      if (dependents.length === 0) {
        // No dependents, latest start = project end - duration
        const duration = durations.get(taskId) ?? 1;
        const latest = projectDuration - duration;
        latestStart.set(taskId, latest);
        return latest;
      }

      // Latest start = min(dependent latest starts) - duration
      let minDependentStart = projectDuration;
      for (const dependent of dependents) {
        const depLatest = latestStart.get(dependent.id) ?? calculateLatestStart(dependent.id);
        minDependentStart = Math.min(minDependentStart, depLatest);
      }

      const duration = durations.get(taskId) ?? 1;
      const latest = minDependentStart - duration;
      latestStart.set(taskId, latest);
      return latest;
    };

    // Calculate latest start for all tasks
    for (const task of tasks) {
      if (!latestStart.has(task.id)) {
        calculateLatestStart(task.id);
      }
    }

    // Identify critical path (tasks with zero slack)
    const criticalTasks: Task[] = [];
    const nonCriticalTasks: Array<{ task: Task; slack: number }> = [];

    for (const task of tasks) {
      const early = earliestStart.get(task.id) ?? 0;
      const late = latestStart.get(task.id) ?? 0;
      const slack = late - early;

      if (slack === 0) {
        criticalTasks.push(task);
      } else {
        nonCriticalTasks.push({ task, slack });
      }
    }

    // Build critical path chain
    const path = criticalTasks
      .sort((a, b) => (earliestStart.get(a.id) ?? 0) - (earliestStart.get(b.id) ?? 0))
      .map((task) => ({
        task,
        duration: durations.get(task.id) ?? 1,
        cumulativeDuration: (earliestStart.get(task.id) ?? 0) + (durations.get(task.id) ?? 1),
      }));

    return {
      path,
      totalDuration: projectDuration,
      nonCriticalTasks,
    };
  }

  /**
   * Find tasks approaching their due date (within specified days)
   */
  async getTasksApproachingDueDate(projectId: string, daysThreshold: number = 7): Promise<Task[]> {
    const tasksResponse = await this.getTasks({ projectId, limit: 1000 });
    const now = new Date();
    const threshold = new Date(now.getTime() + daysThreshold * 24 * 60 * 60 * 1000);

    return tasksResponse.data.filter((task) => {
      if (!task.dueDate || task.status === "COMPLETED") return false;
      const dueDate = new Date(task.dueDate);
      return dueDate >= now && dueDate <= threshold;
    });
  }
}

/**
 * Create a client instance from environment variables
 */
export function createGanttClientFromEnv(): GanttClient {
  const apiUrl = process.env.MOSAIC_API_URL;
  const workspaceId = process.env.MOSAIC_WORKSPACE_ID;
  const apiToken = process.env.MOSAIC_API_TOKEN;

  if (!apiUrl || !workspaceId || !apiToken) {
    throw new Error(
      "Missing required environment variables: MOSAIC_API_URL, MOSAIC_WORKSPACE_ID, MOSAIC_API_TOKEN"
    );
  }

  return new GanttClient({ apiUrl, workspaceId, apiToken });
}
