/**
 * Graph Service - Microsoft Graph API integration
 */

import { env } from '../config/env';
import type { JobFile } from '@shared/types/job';

interface GraphTokenResponse {
  access_token: string;
  refresh_token: string;
  expires_in: number;
  token_type: string;
}

interface GraphDriveItem {
  id: string;
  name: string;
  size: number;
  file?: {
    mimeType: string;
  };
  webUrl: string;
  '@microsoft.graph.downloadUrl'?: string;
  createdDateTime: string;
  lastModifiedDateTime: string;
  createdBy?: {
    user: {
      displayName: string;
      email: string;
    };
  };
  lastModifiedBy?: {
    user: {
      displayName: string;
      email: string;
    };
  };
}

interface GraphListResponse {
  value: GraphDriveItem[];
  '@odata.nextLink'?: string;
}

class GraphService {
  private readonly tokenEndpoint: string;
  private readonly graphBaseUrl = 'https://graph.microsoft.com/v1.0';

  constructor() {
    this.tokenEndpoint = `https://login.microsoftonline.com/${env.MICROSOFT_TENANT}/oauth2/v2.0/token`;
  }

  /**
   * Refresh access token using refresh token
   */
  async refreshToken(refreshToken: string): Promise<{ accessToken: string; refreshToken: string }> {
    const params = new URLSearchParams({
      client_id: env.MICROSOFT_CLIENT_ID,
      client_secret: env.MICROSOFT_CLIENT_SECRET,
      refresh_token: refreshToken,
      grant_type: 'refresh_token',
      scope: 'offline_access Files.Read Sites.Read.All User.Read',
    });

    const response = await fetch(this.tokenEndpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: params.toString(),
    });

    if (!response.ok) {
      const error = await response.text();
      console.error('[Graph] Token refresh failed:', error);
      throw new Error(`Token refresh failed: ${response.status}`);
    }

    const data: GraphTokenResponse = await response.json();
    
    return {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
    };
  }

  /**
   * Get files from a SharePoint drive folder
   */
  async getFiles(
    accessToken: string,
    siteId: string,
    driveId: string,
    folderPath?: string
  ): Promise<{ files: JobFile[]; nextLink?: string }> {
    let endpoint: string;
    
    if (folderPath) {
      endpoint = `${this.graphBaseUrl}/sites/${siteId}/drives/${driveId}/root:/${folderPath}:/children`;
    } else {
      endpoint = `${this.graphBaseUrl}/sites/${siteId}/drives/${driveId}/root/children`;
    }

    const response = await fetch(endpoint, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      const error = await response.text();
      console.error('[Graph] Get files failed:', error);
      throw new Error(`Get files failed: ${response.status}`);
    }

    const data: GraphListResponse = await response.json();

    const files: JobFile[] = data.value
      .filter(item => item.file) // Only files, not folders
      .map(item => ({
        id: item.id,
        name: item.name,
        size: item.size,
        mimeType: item.file?.mimeType ?? 'application/octet-stream',
        webUrl: item.webUrl,
        downloadUrl: item['@microsoft.graph.downloadUrl'],
        createdDateTime: item.createdDateTime,
        lastModifiedDateTime: item.lastModifiedDateTime,
        createdBy: item.createdBy,
        lastModifiedBy: item.lastModifiedBy,
      }));

    return {
      files,
      nextLink: data['@odata.nextLink'],
    };
  }

  /**
   * Get file content/download URL
   */
  async getFileDownloadUrl(
    accessToken: string,
    siteId: string,
    driveId: string,
    itemId: string
  ): Promise<string | null> {
    const endpoint = `${this.graphBaseUrl}/sites/${siteId}/drives/${driveId}/items/${itemId}`;

    const response = await fetch(endpoint, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });

    if (!response.ok) {
      console.error('[Graph] Get download URL failed:', response.status);
      return null;
    }

    const data: GraphDriveItem = await response.json();
    return data['@microsoft.graph.downloadUrl'] ?? null;
  }

  /**
   * Get user profile
   */
  async getUserProfile(accessToken: string): Promise<{ displayName: string; mail: string } | null> {
    const endpoint = `${this.graphBaseUrl}/me`;

    const response = await fetch(endpoint, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });

    if (!response.ok) {
      console.error('[Graph] Get user profile failed:', response.status);
      return null;
    }

    return response.json();
  }

  /**
   * Search for files in SharePoint
   */
  async searchFiles(
    accessToken: string,
    siteId: string,
    query: string
  ): Promise<JobFile[]> {
    const endpoint = `${this.graphBaseUrl}/sites/${siteId}/drive/root/search(q='${encodeURIComponent(query)}')`;

    const response = await fetch(endpoint, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });

    if (!response.ok) {
      console.error('[Graph] Search files failed:', response.status);
      return [];
    }

    const data: GraphListResponse = await response.json();

    return data.value
      .filter(item => item.file)
      .map(item => ({
        id: item.id,
        name: item.name,
        size: item.size,
        mimeType: item.file?.mimeType ?? 'application/octet-stream',
        webUrl: item.webUrl,
        downloadUrl: item['@microsoft.graph.downloadUrl'],
        createdDateTime: item.createdDateTime,
        lastModifiedDateTime: item.lastModifiedDateTime,
        createdBy: item.createdBy,
        lastModifiedBy: item.lastModifiedBy,
      }));
  }
}

export const graphService = new GraphService();
