import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import {
  BaseConnector,
  ConnectionTestResult,
} from '../base-connector';
import { IntegrationConfigError } from '../../errors';
import { CredentialsService } from '../../credentials/credentials.service';
import {
  TodoistAuthConfig,
  TodoistTask,
  TodoistCreateTaskRequest,
  TodoistUpdateTaskRequest,
  TodoistGetTasksRequest,
  TodoistProject,
  TodoistCreateProjectRequest,
  TodoistUpdateProjectRequest,
  TodoistSection,
  TodoistCreateSectionRequest,
  TodoistLabel,
  TodoistCreateLabelRequest,
  TodoistComment,
  TodoistCreateCommentRequest,
} from './todoist.types';

/**
 * Todoist API Connector
 *
 * Provides integration with Todoist task management platform.
 * Uses Bearer Token authentication.
 * Credentials are loaded lazily from the DB via CredentialsService.
 *
 * @see https://developer.todoist.com/rest/v2/
 */
@Injectable()
export class TodoistConnector extends BaseConnector {
  protected readonly name = 'Todoist';
  private authConfig: TodoistAuthConfig = { apiToken: '' };
  private initialized = false;
  private configuredState = false;

  constructor(private readonly credentialsService: CredentialsService) {
    super({
      baseUrl: 'https://api.todoist.com/rest/v2',
      timeout: 30000,
      maxRetries: 3,
    });
  }

  /**
   * Load credentials from DB and configure the connector.
   * Idempotent - returns immediately if already initialized.
   * Note: Todoist uses a fixed base URL; only the API token changes.
   */
  async ensureInitialized(): Promise<void> {
    if (this.initialized) return;

    const result = await this.credentialsService.findActiveByType('TODOIST');

    if (!result) {
      this.configuredState = false;
      this.initialized = true;
      return;
    }

    const { apiToken } = result.credentials;

    this.authConfig = {
      apiToken: apiToken || '',
    };

    this.configuredState = !!this.authConfig.apiToken;
    this.initialized = true;
  }

  /**
   * Reload credentials from DB (called when credentials change)
   */
  async reload(): Promise<void> {
    this.initialized = false;
    await this.ensureInitialized();
  }

  /**
   * Handle credential change events
   */
  @OnEvent('credentials.changed')
  async onCredentialsChanged(payload: { type: string }): Promise<void> {
    if (payload.type === 'TODOIST') {
      this.logger.log('Todoist credentials changed, reloading');
      await this.reload();
    }
  }

  /**
   * Check if the connector is properly configured (last known state)
   */
  isConfigured(): boolean {
    return this.configuredState;
  }

  /**
   * Get missing configuration items
   */
  getMissingConfig(): string[] {
    const missing: string[] = [];
    if (!this.authConfig.apiToken) missing.push('apiToken');
    return missing;
  }

  /**
   * Test the connection to Todoist
   */
  async testConnection(): Promise<ConnectionTestResult> {
    await this.ensureInitialized();

    if (!this.isConfigured()) {
      return {
        success: false,
        message: `Missing configuration: ${this.getMissingConfig().join(', ')}`,
      };
    }

    const startTime = Date.now();

    try {
      await this.getProjects();

      return {
        success: true,
        message: 'Successfully connected to Todoist',
        latencyMs: Date.now() - startTime,
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error';
      return {
        success: false,
        message: `Connection failed: ${message}`,
        latencyMs: Date.now() - startTime,
      };
    }
  }

  /**
   * Get authentication headers for requests (Bearer token)
   */
  async getAuthHeaders(): Promise<Record<string, string>> {
    await this.ensureInitialized();

    if (!this.isConfigured()) {
      throw new IntegrationConfigError(this.name, this.getMissingConfig());
    }

    return {
      Authorization: `Bearer ${this.authConfig.apiToken}`,
    };
  }

  // ===========================================================================
  // Tasks API
  // ===========================================================================

  /**
   * Get active tasks
   */
  async getTasks(request?: TodoistGetTasksRequest): Promise<TodoistTask[]> {
    await this.ensureInitialized();
    const params = this.buildTasksParams(request);
    return this.get<TodoistTask[]>('/tasks', { params });
  }

  /**
   * Get a single task by ID
   */
  async getTask(taskId: string): Promise<TodoistTask> {
    await this.ensureInitialized();
    return this.get<TodoistTask>(`/tasks/${taskId}`);
  }

  /**
   * Create a new task
   */
  async createTask(request: TodoistCreateTaskRequest): Promise<TodoistTask> {
    await this.ensureInitialized();
    const requestId = this.generateRequestId();

    return this.post<TodoistTask>('/tasks', request, {
      headers: {
        'X-Request-Id': requestId,
      },
    });
  }

  /**
   * Update a task
   */
  async updateTask(
    taskId: string,
    request: TodoistUpdateTaskRequest,
  ): Promise<TodoistTask> {
    await this.ensureInitialized();
    const requestId = this.generateRequestId();

    return this.post<TodoistTask>(`/tasks/${taskId}`, request, {
      headers: {
        'X-Request-Id': requestId,
      },
    });
  }

  /**
   * Complete a task
   */
  async completeTask(taskId: string): Promise<void> {
    await this.ensureInitialized();
    await this.post<void>(`/tasks/${taskId}/close`, null);
  }

  /**
   * Reopen a task
   */
  async reopenTask(taskId: string): Promise<void> {
    await this.ensureInitialized();
    await this.post<void>(`/tasks/${taskId}/reopen`, null);
  }

  /**
   * Delete a task
   */
  async deleteTask(taskId: string): Promise<void> {
    await this.ensureInitialized();
    await this.delete<void>(`/tasks/${taskId}`);
  }

  // ===========================================================================
  // Projects API
  // ===========================================================================

  /**
   * Get all projects
   */
  async getProjects(): Promise<TodoistProject[]> {
    await this.ensureInitialized();
    return this.get<TodoistProject[]>('/projects');
  }

  /**
   * Get a single project by ID
   */
  async getProject(projectId: string): Promise<TodoistProject> {
    await this.ensureInitialized();
    return this.get<TodoistProject>(`/projects/${projectId}`);
  }

  /**
   * Create a new project
   */
  async createProject(
    request: TodoistCreateProjectRequest,
  ): Promise<TodoistProject> {
    await this.ensureInitialized();
    const requestId = this.generateRequestId();

    return this.post<TodoistProject>('/projects', request, {
      headers: {
        'X-Request-Id': requestId,
      },
    });
  }

  /**
   * Update a project
   */
  async updateProject(
    projectId: string,
    request: TodoistUpdateProjectRequest,
  ): Promise<TodoistProject> {
    await this.ensureInitialized();
    const requestId = this.generateRequestId();

    return this.post<TodoistProject>(`/projects/${projectId}`, request, {
      headers: {
        'X-Request-Id': requestId,
      },
    });
  }

  /**
   * Delete a project
   */
  async deleteProject(projectId: string): Promise<void> {
    await this.ensureInitialized();
    await this.delete<void>(`/projects/${projectId}`);
  }

  // ===========================================================================
  // Sections API
  // ===========================================================================

  /**
   * Get all sections (optionally filtered by project)
   */
  async getSections(projectId?: string): Promise<TodoistSection[]> {
    await this.ensureInitialized();
    const params = projectId ? { project_id: projectId } : {};
    return this.get<TodoistSection[]>('/sections', { params });
  }

  /**
   * Get a single section by ID
   */
  async getSection(sectionId: string): Promise<TodoistSection> {
    await this.ensureInitialized();
    return this.get<TodoistSection>(`/sections/${sectionId}`);
  }

  /**
   * Create a new section
   */
  async createSection(
    request: TodoistCreateSectionRequest,
  ): Promise<TodoistSection> {
    await this.ensureInitialized();
    const requestId = this.generateRequestId();

    return this.post<TodoistSection>('/sections', request, {
      headers: {
        'X-Request-Id': requestId,
      },
    });
  }

  /**
   * Delete a section
   */
  async deleteSection(sectionId: string): Promise<void> {
    await this.ensureInitialized();
    await this.delete<void>(`/sections/${sectionId}`);
  }

  // ===========================================================================
  // Labels API
  // ===========================================================================

  /**
   * Get all personal labels
   */
  async getLabels(): Promise<TodoistLabel[]> {
    await this.ensureInitialized();
    return this.get<TodoistLabel[]>('/labels');
  }

  /**
   * Get a single label by ID
   */
  async getLabel(labelId: string): Promise<TodoistLabel> {
    await this.ensureInitialized();
    return this.get<TodoistLabel>(`/labels/${labelId}`);
  }

  /**
   * Create a new label
   */
  async createLabel(request: TodoistCreateLabelRequest): Promise<TodoistLabel> {
    await this.ensureInitialized();
    const requestId = this.generateRequestId();

    return this.post<TodoistLabel>('/labels', request, {
      headers: {
        'X-Request-Id': requestId,
      },
    });
  }

  /**
   * Delete a label
   */
  async deleteLabel(labelId: string): Promise<void> {
    await this.ensureInitialized();
    await this.delete<void>(`/labels/${labelId}`);
  }

  // ===========================================================================
  // Comments API
  // ===========================================================================

  /**
   * Get comments for a task or project
   */
  async getComments(
    taskId?: string,
    projectId?: string,
  ): Promise<TodoistComment[]> {
    await this.ensureInitialized();
    const params: Record<string, string> = {};
    if (taskId) params.task_id = taskId;
    if (projectId) params.project_id = projectId;

    return this.get<TodoistComment[]>('/comments', { params });
  }

  /**
   * Create a comment
   */
  async createComment(
    request: TodoistCreateCommentRequest,
  ): Promise<TodoistComment> {
    await this.ensureInitialized();
    const requestId = this.generateRequestId();

    return this.post<TodoistComment>('/comments', request, {
      headers: {
        'X-Request-Id': requestId,
      },
    });
  }

  /**
   * Delete a comment
   */
  async deleteComment(commentId: string): Promise<void> {
    await this.ensureInitialized();
    await this.delete<void>(`/comments/${commentId}`);
  }

  // ===========================================================================
  // Helper Methods
  // ===========================================================================

  /**
   * Build query parameters for tasks endpoint
   */
  private buildTasksParams(
    request?: TodoistGetTasksRequest,
  ): Record<string, string> {
    if (!request) return {};

    const params: Record<string, string> = {};

    if (request.project_id) {
      params.project_id = request.project_id;
    }

    if (request.section_id) {
      params.section_id = request.section_id;
    }

    if (request.label) {
      params.label = request.label;
    }

    if (request.filter) {
      params.filter = request.filter;
    }

    if (request.lang) {
      params.lang = request.lang;
    }

    if (request.ids?.length) {
      params.ids = request.ids.join(',');
    }

    return params;
  }

  /**
   * Generate a unique request ID for idempotency
   */
  private generateRequestId(): string {
    return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
  }
}
