/**
 * GitHub repository statistics collector with enhanced metrics using Octokit SDK and GraphQL
 */

import { Octokit } from '@octokit/rest';
import { graphql } from '@octokit/graphql';
import type { MetricResult } from './types';

const PlatformSettings = {
  name: 'GitHub',
}

// GraphQL query for basic repository data (without releases)
const REPOSITORY_BASIC_QUERY = `
  query RepositoryBasicData($owner: String!, $name: String!) {
    repository(owner: $owner, name: $name) {
      id
      name
      description
      homepageUrl
      stargazerCount
      forkCount
      watchers {
        totalCount
      }
      openIssues: issues(states: OPEN) {
        totalCount
      }
      closedIssues: issues(states: CLOSED) {
        totalCount
      }
      primaryLanguage {
        name
      }
      diskUsage
      createdAt
      updatedAt
      pushedAt
      defaultBranchRef {
        name
      }
      repositoryTopics(first: 10) {
        nodes {
          topic {
            name
          }
        }
      }
      licenseInfo {
        name
        spdxId
      }
    }
  }
`;

// GraphQL query for releases with download data
const RELEASES_QUERY = `
  query RepositoryReleases($owner: String!, $name: String!, $first: Int!) {
    repository(owner: $owner, name: $name) {
      releases(first: $first, orderBy: {field: CREATED_AT, direction: DESC}) {
        nodes {
          id
          tagName
          name
          description
          createdAt
          publishedAt
          releaseAssets(first: 100) {
            nodes {
              id
              name
              size
              downloadCount
              downloadUrl
            }
          }
        }
      }
    }
  }
`;

// Interface for basic repository data
interface GraphQLRepositoryBasicResponse {
  repository: {
    id: string;
    name: string;
    description: string | null;
    homepageUrl: string | null;
    stargazerCount: number;
    forkCount: number;
    watchers: {
      totalCount: number;
    };
    openIssues: {
      totalCount: number;
    };
    closedIssues: {
      totalCount: number;
    };
    primaryLanguage: {
      name: string;
    } | null;
    diskUsage: number;
    createdAt: string;
    updatedAt: string;
    pushedAt: string;
    defaultBranchRef: {
      name: string;
    } | null;
    repositoryTopics: {
      nodes: Array<{
        topic: {
          name: string;
        };
      }>;
    };
    licenseInfo: {
      name: string;
      spdxId: string;
    } | null;
  } | null;
}

// Interface for releases data
interface GraphQLReleasesResponse {
  repository: {
    releases: {
      nodes: Array<{
        id: string;
        tagName: string;
        name: string | null;
        description: string | null;
        createdAt: string;
        publishedAt: string | null;
        releaseAssets: {
          nodes: Array<{
            id: string;
            name: string;
            size: number;
            downloadCount: number;
            downloadUrl: string;
          } | null>;
        };
      } | null>;
    };
  } | null;
}


  export async function collectGithub(repository: string): Promise<MetricResult> {
    try {
      const [owner, repo] = repository.split('/');
      
      if (!owner || !repo) {
        throw new Error(`Invalid repository format: ${repository}. Expected "owner/repo"`);
      }
      
      // Initialize Octokit for REST API calls
      const token = process.env.GITHUB_TOKEN || process.env.INPUT_GITHUB_TOKEN || '';
      const octokit = new Octokit({
        auth: token,
        userAgent: 'usage-statistics-tracker'
      });
      
      if (!token) {
        console.warn('No GitHub token provided. Using unauthenticated requests (rate limited).');
      }
      
      // Step 1: Fetch basic repository data using GraphQL
      let graphqlData: any = null;
      
      try {
        const graphqlClient = graphql.defaults({
          headers: {
            authorization: token ? `token ${token}` : undefined,
          },
        });
        
        // Fetch basic repository data (without releases)
        const basicResponse = await graphqlClient<GraphQLRepositoryBasicResponse>(REPOSITORY_BASIC_QUERY, {
          owner,
          name: repo
        });
        
        if (basicResponse.repository) {
          graphqlData = basicResponse.repository;
        }
      } catch (error) {
        console.warn(`Could not fetch GitHub GraphQL basic data for ${repository}:`, error);
      }
      
      // Step 2: Fetch releases data separately using GraphQL
      let totalReleaseDownloads = 0;
      let latestReleaseDownloads = 0;
      let releaseCount = 0;
      let downloadRange = [];
      let latestRelease = null;
      
      try {
        const graphqlClient = graphql.defaults({
          headers: {
            authorization: token ? `token ${token}` : undefined,
          },
        });
        
        // Fetch releases data
        const releasesResponse = await graphqlClient<GraphQLReleasesResponse>(RELEASES_QUERY, {
          owner,
          name: repo,
          first: 100
        });
        
        if (releasesResponse.repository?.releases?.nodes) {
          const releases = releasesResponse.repository.releases.nodes.filter(Boolean);
          releaseCount = releases.length;
          
          for (const release of releases) {
            let releaseDownloads = 0;
            if (release?.releaseAssets?.nodes) {
              for (const asset of release.releaseAssets.nodes) {
                if (asset) {
                  releaseDownloads += asset.downloadCount || 0;
                }
              }
            }
            totalReleaseDownloads += releaseDownloads;
            
            // Latest release is the first one in the list
            if (release && release === releases[0]) {
              latestReleaseDownloads = releaseDownloads;
              latestRelease = release.tagName;
            }
            
            // Add to download range with proper date format for charts
            if (release?.publishedAt) {
              downloadRange.push({
                day: release.publishedAt,
                downloads: releaseDownloads,
                tagName: release.tagName
              });
            }
          }
        }
      } catch (error) {
        console.warn(`Could not fetch GitHub GraphQL releases data for ${repository}:`, error);
      }
      
      // Fallback to REST API if GraphQL fails or for additional data
      let restData: any = null;
      
      try {
        const { data: repoData } = await octokit.repos.get({
          owner,
          repo
        });
        restData = repoData;
      } catch (error) {
        console.warn(`Could not fetch GitHub REST data for ${repository}:`, error);
      }
      
      // Use the best available data (GraphQL preferred, REST as fallback)
      const finalData = graphqlData || restData;
      
      if (!finalData) {
        throw new Error('Could not fetch repository data from either GraphQL or REST API');
      }
      
      // Get traffic statistics using REST API (requires authentication)
      let viewsCount = 0;
      let uniqueVisitors = 0;
      let clonesCount = 0;
      
      if (token) {
        try {
          // Get views data
          const { data: viewsData } = await octokit.repos.getViews({
            owner,
            repo
          });
          
          if (viewsData) {
            viewsCount = viewsData.count || 0;
            uniqueVisitors = viewsData.uniques || 0;
          }
          
          // Get clones data
          const { data: clonesData } = await octokit.repos.getClones({
            owner,
            repo
          });
          
          if (clonesData) {
            clonesCount = clonesData.count || 0;
          }
        } catch (error) {
          console.warn(`Could not fetch GitHub traffic data for ${repository}:`, error);
        }
      }
      
      // Calculate repository age
      let repositoryAge = 0;
      if (finalData.createdAt) {
        const created = new Date(finalData.createdAt);
        const now = new Date();
        repositoryAge = Math.floor((now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24)); // days
      }
      
      // Calculate activity metrics
      let lastActivity = 0;
      if (finalData.pushedAt) {
        const pushed = new Date(finalData.pushedAt);
        const now = new Date();
        lastActivity = Math.floor((now.getTime() - pushed.getTime()) / (1000 * 60 * 60 * 24)); // days
      }
      
      return {
        platform: PlatformSettings.name,
        name: repository,
        timestamp: new Date().toISOString(),
        metrics: {
          stars: finalData.stargazerCount || finalData.stargazers_count || 0,
          forks: finalData.forkCount || finalData.forks_count || 0,
          watchers: finalData.watchers?.totalCount || finalData.watchers_count || 0,
          totalIssues: finalData.openIssues?.totalCount + finalData.closedIssues?.totalCount || 0,
          openIssues: finalData.openIssues?.totalCount || 0,
          closedIssues: finalData.closedIssues?.totalCount || 0,
          language: finalData.primaryLanguage?.name || finalData.language || null,
          size: finalData.diskUsage || finalData.size || null,
          repositoryAge,
          lastActivity,
          releaseCount,
          totalReleaseDownloads,
          latestReleaseDownloads,
          viewsCount,
          uniqueVisitors,
          latestRelease,
          clonesCount,
          topics: finalData.repositoryTopics?.nodes?.length || finalData.topics?.length || 0,
          license: finalData.licenseInfo?.name || finalData.license?.name || null,
          defaultBranch: finalData.defaultBranchRef?.name || finalData.default_branch || null,
          downloadsTotal: totalReleaseDownloads || 0,
          downloadRange,
        }
      };
    } catch (error) {
      return {
        platform: PlatformSettings.name,
        name: repository,
        timestamp: new Date().toISOString(),
        metrics: {},
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }
  
  export async function collectGithubBatch(repositories: string[]): Promise<MetricResult[]> {
    const results: Promise<MetricResult>[] = [];
    
    for (const repo of repositories) {
      results.push(collectGithub(repo));
    }
    
    return Promise.all(results);
  }
