import { Gitlab } from '@gitbeaker/rest';
import { GitLabActivity } from '../types/index.js';
import { startOfDay, endOfDay } from 'date-fns';

export class GitLabIntegration {
  private client: InstanceType<typeof Gitlab> | null = null;
  private userId: number | null = null;

  async initialize(token: string, gitlabUrl: string = 'https://gitlab.com'): Promise<void> {
    this.client = new Gitlab({
      token,
      host: gitlabUrl,
    });

    // Get current user ID
    const user = await this.client.Users.showCurrentUser();
    this.userId = user.id;
  }

  async getActivityForDate(date: Date): Promise<GitLabActivity> {
    if (!this.client || !this.userId) {
      throw new Error('GitLab client not initialized');
    }

    const dayStart = startOfDay(date);
    const dayEnd = endOfDay(date);

    const activity: GitLabActivity = {
      date,
      commits: [],
      mergeRequests: [],
      issues: [],
    };

    try {
      // Get user's events for the day
      // GitLab API: after means >= date, before means <= date
      const events = await this.client.Events.all({
        after: dayStart.toISOString().split('T')[0],
      });

      for (const event of events) {
        if (event.author_id !== this.userId) continue;

        // Filter events to only include those within the target day
        const eventDate = new Date(event.created_at as string);
        if (eventDate < dayStart || eventDate > dayEnd) continue;

        const projectName = event.project_id && typeof event.project_id === 'number'
          ? await this.getProjectName(event.project_id)
          : 'Unknown';

        switch (event.action_name) {
          case 'pushed to':
          case 'pushed new':
            if (event.push_data && typeof event.push_data === 'object') {
              const pushData = event.push_data as any;
              activity.commits.push({
                message: String(pushData.commit_title || 'Commit'),
                project: projectName,
                branch: String(pushData.ref || 'unknown'),
              });
            }
            break;

          case 'opened':
            if (event.target_type === 'MergeRequest') {
              activity.mergeRequests.push({
                action: 'created',
                title: String(event.target_title || 'MR'),
                project: projectName,
              });
            } else if (event.target_type === 'Issue') {
              activity.issues.push({
                action: 'status_changed',
                title: String(event.target_title || 'Issue'),
                project: projectName,
                details: 'opened',
              });
            }
            break;

          case 'commented on':
            if (event.target_type === 'MergeRequest') {
              activity.mergeRequests.push({
                action: 'commented',
                title: String(event.target_title || 'MR'),
                project: projectName,
              });
            } else if (event.target_type === 'Issue') {
              activity.issues.push({
                action: 'commented',
                title: String(event.target_title || 'Issue'),
                project: projectName,
              });
            }
            break;

          case 'accepted':
            if (event.target_type === 'MergeRequest') {
              activity.mergeRequests.push({
                action: 'approved',
                title: String(event.target_title || 'MR'),
                project: projectName,
              });
            }
            break;

          case 'approved':
            if (event.target_type === 'MergeRequest') {
              activity.mergeRequests.push({
                action: 'approved',
                title: String(event.target_title || 'MR'),
                project: projectName,
              });
            }
            break;
        }
      }

      // Also fetch commits directly for more detailed information
      await this.fetchCommitsForDate(date, activity);

      // Fetch MR reviews
      await this.fetchMergeRequestActivity(date, activity);

    } catch (error) {
      console.error('Error fetching GitLab activity:', error);
    }

    return activity;
  }

  private async fetchCommitsForDate(date: Date, activity: GitLabActivity): Promise<void> {
    if (!this.client || !this.userId) return;

    try {
      // Get all projects the user has access to
      const projects = await this.client.Projects.all({
        membership: true,
        simple: true,
        perPage: 100,
      });

      const dayStart = startOfDay(date);
      const dayEnd = endOfDay(date);

      for (const project of projects) {
        try {
          const commits = await this.client.Commits.all(project.id, {
            since: dayStart.toISOString(),
            until: dayEnd.toISOString(),
            author: await this.getCurrentUserEmail(),
          });

          for (const commit of commits) {
            // Avoid duplicates
            const exists = activity.commits.some(c => c.message === commit.title);
            if (!exists) {
              activity.commits.push({
                message: commit.title,
                project: project.name,
                branch: 'main', // GitLab API doesn't always provide branch in commit list
              });
            }
          }
        } catch (error) {
          // Skip projects we can't access
          continue;
        }
      }
    } catch (error) {
      console.error('Error fetching commits:', error);
    }
  }

  private async fetchMergeRequestActivity(date: Date, activity: GitLabActivity): Promise<void> {
    if (!this.client || !this.userId) return;

    try {
      const dayStart = startOfDay(date);
      const dayEnd = endOfDay(date);

      // Get MRs created by user
      const createdMRs = await this.client.MergeRequests.all({
        authorId: this.userId,
        createdAfter: dayStart.toISOString(),
        createdBefore: dayEnd.toISOString(),
        scope: 'all',
      });

      for (const mr of createdMRs) {
        const projectId = typeof mr.project_id === 'number' ? mr.project_id : 0;
        const projectName = await this.getProjectName(projectId);
        activity.mergeRequests.push({
          action: 'created',
          title: mr.title,
          project: projectName,
        });
      }

      // Get MRs where user is reviewer
      const reviewMRs = await this.client.MergeRequests.all({
        reviewerId: this.userId,
        updatedAfter: dayStart.toISOString(),
        updatedBefore: dayEnd.toISOString(),
        scope: 'all',
      });

      for (const mr of reviewMRs) {
        const projectId = typeof mr.project_id === 'number' ? mr.project_id : 0;
        const projectName = await this.getProjectName(projectId);
        // Check if already added
        const exists = activity.mergeRequests.some(
          m => m.title === mr.title && m.project === projectName
        );
        if (!exists) {
          activity.mergeRequests.push({
            action: 'reviewed',
            title: mr.title,
            project: projectName,
          });
        }
      }
    } catch (error) {
      console.error('Error fetching MR activity:', error);
    }
  }

  private projectNameCache = new Map<number, string>();

  private async getProjectName(projectId: number): Promise<string> {
    if (this.projectNameCache.has(projectId)) {
      return this.projectNameCache.get(projectId)!;
    }

    try {
      const project = await this.client!.Projects.show(projectId);
      this.projectNameCache.set(projectId, project.name);
      return project.name;
    } catch {
      return `Project ${projectId}`;
    }
  }

  private async getCurrentUserEmail(): Promise<string> {
    if (!this.client) throw new Error('Client not initialized');
    const user = await this.client.Users.showCurrentUser();
    return user.email;
  }
}
