import { describe, test, expect } from 'vitest';
import { parseWebProfileInfo, parseTimelineConnection, shortcodeToPk, parseMediaInfoViewCount, parseReelsClipsConnection } from './parse.js';

// Representative shape of i.instagram.com/api/v1/users/web_profile_info.
// NOTE: live IG response shape must be verified; parser is the swappable adapter (PRD NFR-2).
const fixture = {
  data: {
    user: {
      id: '999',
      username: 'tem.duck',
      full_name: '템덕',
      edge_followed_by: { count: 207378 },
      edge_owner_to_timeline_media: {
        count: 224,
        edges: [
          {
            node: {
              __typename: 'GraphVideo',
              shortcode: 'ABC123',
              product_type: 'clips',
              is_video: true,
              taken_at_timestamp: 1704067200, // 2024-01-01T00:00:00Z
              display_url: 'https://scontent/abc.jpg',
              video_view_count: 50000,
              edge_media_to_comment: { count: 14780 },
              edge_media_to_caption: { edges: [{ node: { text: '이거 먹으면 키 큰다고?!' } }] },
            },
          },
          {
            node: {
              __typename: 'GraphImage',
              shortcode: 'IMG777',
              product_type: 'feed',
              is_video: false,
              taken_at_timestamp: 1735689600, // 2025-01-01T00:00:00Z
              display_url: 'https://scontent/img.jpg',
              edge_media_to_comment: { count: 12 },
              edge_media_to_caption: { edges: [] },
            },
          },
        ],
      },
    },
  },
};

describe('parseWebProfileInfo', () => {
  test('extracts profile-level fields', () => {
    const p = parseWebProfileInfo(fixture);
    expect(p.username).toBe('tem.duck');
    expect(p.displayName).toBe('템덕');
    expect(p.followerCount).toBe(207378);
    expect(p.postCount).toBe(224);
    expect(p.posts).toHaveLength(2);
  });

  test('parses a reel (clips) node', () => {
    const reel = parseWebProfileInfo(fixture).posts[0]!;
    expect(reel.shortcode).toBe('ABC123');
    expect(reel.type).toBe('reel');
    expect(reel.url).toBe('https://www.instagram.com/reel/ABC123/');
    expect(reel.uploadedAt).toBe('2024-01-01T00:00:00.000Z');
    expect(reel.viewCount).toBe(50000);
    expect(reel.commentCount).toBe(14780);
    expect(reel.caption).toBe('이거 먹으면 키 큰다고?!');
    expect(reel.thumbnail).toBe('https://scontent/abc.jpg');
  });

  test('parses a regular post node with no caption/views', () => {
    const post = parseWebProfileInfo(fixture).posts[1]!;
    expect(post.type).toBe('post');
    expect(post.url).toBe('https://www.instagram.com/p/IMG777/');
    expect(post.caption).toBeNull();
    expect(post.viewCount).toBeNull();
    expect(post.commentCount).toBe(12);
  });

  test('throws on an unexpected shape (missing user)', () => {
    expect(() => parseWebProfileInfo({})).toThrow();
  });
});

// Logged-in: posts come from the GraphQL timeline connection (xdt_api shape, snake_case).
const timelineFixture = {
  data: {
    xdt_api__v1__feed__user_timeline_graphql_connection: {
      edges: [
        {
          node: {
            code: 'DZ2TT3iBHhm',
            product_type: 'clips',
            media_type: 2,
            taken_at: 1704067200, // 2024-01-01T00:00:00Z
            caption: { text: '템덕 릴스 캡션' },
            comment_count: 2272,
            like_count: 237,
            view_count: null,
            play_count: 88000,
            image_versions2: { candidates: [{ url: 'https://scontent/thumb.jpg', width: 1080, height: 1920 }] },
          },
        },
        {
          node: {
            code: 'POST456',
            product_type: 'feed',
            media_type: 1,
            taken_at: 1735689600, // 2025-01-01T00:00:00Z
            caption: null,
            comment_count: 12,
            image_versions2: { candidates: [{ url: 'https://scontent/p.jpg' }] },
          },
        },
      ],
    },
  },
};

describe('parseTimelineConnection', () => {
  test('parses clips nodes as reels', () => {
    const reel = parseTimelineConnection(timelineFixture)[0]!;
    expect(reel.shortcode).toBe('DZ2TT3iBHhm');
    expect(reel.type).toBe('reel');
    expect(reel.url).toBe('https://www.instagram.com/reel/DZ2TT3iBHhm/');
    expect(reel.uploadedAt).toBe('2024-01-01T00:00:00.000Z');
    expect(reel.commentCount).toBe(2272);
    expect(reel.viewCount).toBe(88000); // falls back to play_count when view_count is null
    expect(reel.caption).toBe('템덕 릴스 캡션');
    expect(reel.thumbnail).toBe('https://scontent/thumb.jpg');
  });

  test('parses feed nodes as posts with null caption', () => {
    const post = parseTimelineConnection(timelineFixture)[1]!;
    expect(post.type).toBe('post');
    expect(post.url).toBe('https://www.instagram.com/p/POST456/');
    expect(post.caption).toBeNull();
    expect(post.commentCount).toBe(12);
  });

  test('returns [] when the connection is missing or empty', () => {
    expect(parseTimelineConnection({})).toEqual([]);
    expect(parseTimelineConnection({ data: { xdt_api__v1__feed__user_timeline_graphql_connection: { edges: [] } } })).toEqual([]);
  });
});

describe('shortcodeToPk', () => {
  // Verified against live /api/v1/media/{pk}/info/ responses (matching like/comment counts).
  test('decodes shortcodes to their numeric media pk', () => {
    expect(shortcodeToPk('DZ2TT3iBHhm')).toBe('3924409053480253542');
    expect(shortcodeToPk('DZ8lS2vznBU')).toBe('3926176998615838804');
  });

  test('returns null on empty or invalid input', () => {
    expect(shortcodeToPk('')).toBeNull();
    expect(shortcodeToPk('has space')).toBeNull();
  });
});

describe('parseMediaInfoViewCount', () => {
  test('reads play_count off the first media item', () => {
    expect(parseMediaInfoViewCount({ items: [{ play_count: 140629, ig_play_count: 140629 }] })).toBe(140629);
  });

  test('falls back ig_play_count -> view_count', () => {
    expect(parseMediaInfoViewCount({ items: [{ ig_play_count: 42 }] })).toBe(42);
    expect(parseMediaInfoViewCount({ items: [{ view_count: 7 }] })).toBe(7);
  });

  test('returns null when no view metric (e.g. photo) or empty response', () => {
    expect(parseMediaInfoViewCount({ items: [{ like_count: 5 }] })).toBeNull();
    expect(parseMediaInfoViewCount({ items: [] })).toBeNull();
    expect(parseMediaInfoViewCount({})).toBeNull();
  });
});

describe('parseReelsClipsConnection', () => {
  // Shape from the reels tab (/{user}/reels/) GraphQL: PolarisProfileReelsTabContentQuery
  // → data.xdt_api__v1__clips__user__connection_v2.edges[].node.media.play_count.
  const clips = {
    data: {
      xdt_api__v1__clips__user__connection_v2: {
        page_info: { has_next_page: true },
        edges: [
          { node: { media: { code: 'AAA', play_count: 123456, comment_count: 78, like_count: 900 } } },
          { node: { media: { code: 'BBB', play_count: 0, comment_count: 3 } } },
          { node: { media: { code: 'CCC', comment_count: 5 } } }, // no play_count → 0
          { node: { other: 1 } }, // no media → skipped
        ],
      },
    },
  };
  test('extracts shortcode + play_count as view counts', () => {
    const r = parseReelsClipsConnection(clips);
    expect(r).toEqual([
      { shortcode: 'AAA', viewCount: 123456 },
      { shortcode: 'BBB', viewCount: 0 },
      { shortcode: 'CCC', viewCount: 0 },
    ]);
  });
  test('falls back to ig_play_count / view_count aliases', () => {
    const r = parseReelsClipsConnection({
      data: { xdt_api__v1__clips__user__connection_v2: { edges: [
        { node: { media: { code: 'X', ig_play_count: 42 } } },
        { node: { media: { code: 'Y', view_count: 7 } } },
      ] } },
    });
    expect(r).toEqual([{ shortcode: 'X', viewCount: 42 }, { shortcode: 'Y', viewCount: 7 }]);
  });
  test('returns [] on an unexpected / empty shape', () => {
    expect(parseReelsClipsConnection({})).toEqual([]);
    expect(parseReelsClipsConnection({ data: {} })).toEqual([]);
    expect(parseReelsClipsConnection(null)).toEqual([]);
  });
});
