import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
  createComment,
  getComments,
  updateComment,
  deleteComment,
  getReplies,
  CommentError,
  TrackComment,
  CommentListResponse,
  ReplyListResponse,
} from './commentService';
import { apiClient } from '@/services/api/client';
import { AxiosError } from 'axios';

// Mock apiClient
vi.mock('@/services/api/client', () => ({
  apiClient: {
    post: vi.fn(),
    get: vi.fn(),
    put: vi.fn(),
    delete: vi.fn(),
  },
}));

describe('commentService', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('createComment', () => {
    it('should create a comment successfully', async () => {
      const mockComment: TrackComment = {
        id: 1,
        track_id: 1,
        creator_id: 123,
        content: 'Great track!',
        is_edited: false,
        created_at: '2024-01-01T00:00:00Z',
        updated_at: '2024-01-01T00:00:00Z',
        user: {
          id: 123,
          username: 'testuser',
        },
      };

      vi.mocked(apiClient.post).mockResolvedValue({
        data: { comment: mockComment },
        status: 201,
        statusText: 'Created',
        headers: {},
        config: {} as any,
      });

      const result = await createComment(1, 'Great track!');

      expect(result).toEqual(mockComment);
      expect(apiClient.post).toHaveBeenCalledWith('/tracks/1/comments', {
        content: 'Great track!',
        parent_id: undefined,
      });
    });

    it('should create a reply comment successfully', async () => {
      const mockReply: TrackComment = {
        id: 2,
        track_id: 1,
        creator_id: 123,
        parent_id: 1,
        content: 'Reply to comment',
        is_edited: false,
        created_at: '2024-01-01T00:00:00Z',
        updated_at: '2024-01-01T00:00:00Z',
        user: {
          id: 123,
          username: 'testuser',
        },
      };

      vi.mocked(apiClient.post).mockResolvedValue({
        data: { comment: mockReply },
        status: 201,
        statusText: 'Created',
        headers: {},
        config: {} as any,
      });

      const result = await createComment(1, 'Reply to comment', 1);

      expect(result).toEqual(mockReply);
      expect(apiClient.post).toHaveBeenCalledWith('/tracks/1/comments', {
        content: 'Reply to comment',
        parent_id: 1,
      });
    });

    it('should throw CommentError for 401 Unauthorized', async () => {
      const axiosError = new AxiosError('Unauthorized');
      axiosError.response = {
        status: 401,
        statusText: 'Unauthorized',
        data: {},
        headers: {},
        config: {} as any,
      };

      vi.mocked(apiClient.post).mockRejectedValue(axiosError);

      await expect(createComment(1, 'Comment')).rejects.toThrow(CommentError);
      try {
        await createComment(1, 'Comment');
      } catch (error) {
        expect(error).toBeInstanceOf(CommentError);
        expect((error as CommentError).code).toBe('VALIDATION');
        expect((error as CommentError).retryable).toBe(false);
      }
    });

    it('should throw CommentError for 404 Not Found', async () => {
      const axiosError = new AxiosError('Not Found');
      axiosError.response = {
        status: 404,
        statusText: 'Not Found',
        data: {},
        headers: {},
        config: {} as any,
      };

      vi.mocked(apiClient.post).mockRejectedValue(axiosError);

      await expect(createComment(999, 'Comment')).rejects.toThrow(CommentError);
      try {
        await createComment(999, 'Comment');
      } catch (error) {
        expect(error).toBeInstanceOf(CommentError);
        expect((error as CommentError).code).toBe('VALIDATION');
        expect((error as CommentError).retryable).toBe(false);
      }
    });

    it('should throw CommentError for 500 Internal Server Error', async () => {
      const axiosError = new AxiosError('Internal Server Error');
      axiosError.response = {
        status: 500,
        statusText: 'Internal Server Error',
        data: {},
        headers: {},
        config: {} as any,
      };

      vi.mocked(apiClient.post).mockRejectedValue(axiosError);

      await expect(createComment(1, 'Comment')).rejects.toThrow(CommentError);
      try {
        await createComment(1, 'Comment');
      } catch (error) {
        expect(error).toBeInstanceOf(CommentError);
        expect((error as CommentError).code).toBe('SERVER');
        expect((error as CommentError).retryable).toBe(true);
      }
    });
  });

  describe('getComments', () => {
    it('should get comments successfully', async () => {
      const mockResponse: CommentListResponse = {
        comments: [
          {
            id: 1,
            track_id: 1,
            creator_id: 123,
            content: 'Comment 1',
            is_edited: false,
            created_at: '2024-01-01T00:00:00Z',
            updated_at: '2024-01-01T00:00:00Z',
            user: {
              id: 123,
              username: 'testuser',
            },
          },
        ],
        total: 1,
        page: 1,
        limit: 20,
      };

      vi.mocked(apiClient.get).mockResolvedValue({
        data: mockResponse,
        status: 200,
        statusText: 'OK',
        headers: {},
        config: {} as any,
      });

      const result = await getComments(1);

      expect(result).toEqual(mockResponse);
      expect(apiClient.get).toHaveBeenCalledWith(
        '/tracks/1/comments?page=1&limit=20',
      );
    });

    it('should get comments with pagination', async () => {
      const mockResponse: CommentListResponse = {
        comments: [],
        total: 50,
        page: 2,
        limit: 10,
      };

      vi.mocked(apiClient.get).mockResolvedValue({
        data: mockResponse,
        status: 200,
        statusText: 'OK',
        headers: {},
        config: {} as any,
      });

      const result = await getComments(1, 2, 10);

      expect(result).toEqual(mockResponse);
      expect(apiClient.get).toHaveBeenCalledWith(
        '/tracks/1/comments?page=2&limit=10',
      );
    });

    it('should throw CommentError for 404 Not Found', async () => {
      const axiosError = new AxiosError('Not Found');
      axiosError.response = {
        status: 404,
        statusText: 'Not Found',
        data: {},
        headers: {},
        config: {} as any,
      };

      vi.mocked(apiClient.get).mockRejectedValue(axiosError);

      await expect(getComments(999)).rejects.toThrow(CommentError);
      try {
        await getComments(999);
      } catch (error) {
        expect(error).toBeInstanceOf(CommentError);
        expect((error as CommentError).code).toBe('VALIDATION');
        expect((error as CommentError).retryable).toBe(false);
      }
    });
  });

  describe('updateComment', () => {
    it('should update a comment successfully', async () => {
      const mockComment: TrackComment = {
        id: 1,
        track_id: 1,
        creator_id: 123,
        content: 'Updated content',
        is_edited: true,
        created_at: '2024-01-01T00:00:00Z',
        updated_at: '2024-01-02T00:00:00Z',
        user: {
          id: 123,
          username: 'testuser',
        },
      };

      vi.mocked(apiClient.put).mockResolvedValue({
        data: { comment: mockComment },
        status: 200,
        statusText: 'OK',
        headers: {},
        config: {} as any,
      });

      const result = await updateComment(1, 'Updated content');

      expect(result).toEqual(mockComment);
      expect(apiClient.put).toHaveBeenCalledWith('/comments/1', {
        content: 'Updated content',
      });
    });

    it('should throw CommentError for 403 Forbidden', async () => {
      const axiosError = new AxiosError('Forbidden');
      axiosError.response = {
        status: 403,
        statusText: 'Forbidden',
        data: {},
        headers: {},
        config: {} as any,
      };

      vi.mocked(apiClient.put).mockRejectedValue(axiosError);

      await expect(updateComment(1, 'Updated')).rejects.toThrow(CommentError);
      try {
        await updateComment(1, 'Updated');
      } catch (error) {
        expect(error).toBeInstanceOf(CommentError);
        expect((error as CommentError).code).toBe('VALIDATION');
        expect((error as CommentError).retryable).toBe(false);
      }
    });

    it('should throw CommentError for 404 Not Found', async () => {
      const axiosError = new AxiosError('Not Found');
      axiosError.response = {
        status: 404,
        statusText: 'Not Found',
        data: {},
        headers: {},
        config: {} as any,
      };

      vi.mocked(apiClient.put).mockRejectedValue(axiosError);

      await expect(updateComment(999, 'Updated')).rejects.toThrow(CommentError);
      try {
        await updateComment(999, 'Updated');
      } catch (error) {
        expect(error).toBeInstanceOf(CommentError);
        expect((error as CommentError).code).toBe('VALIDATION');
        expect((error as CommentError).retryable).toBe(false);
      }
    });
  });

  describe('deleteComment', () => {
    it('should delete a comment successfully', async () => {
      vi.mocked(apiClient.delete).mockResolvedValue({
        data: {},
        status: 200,
        statusText: 'OK',
        headers: {},
        config: {} as any,
      });

      await deleteComment(1);

      expect(apiClient.delete).toHaveBeenCalledWith('/comments/1');
    });

    it('should throw CommentError for 403 Forbidden', async () => {
      const axiosError = new AxiosError('Forbidden');
      axiosError.response = {
        status: 403,
        statusText: 'Forbidden',
        data: {},
        headers: {},
        config: {} as any,
      };

      vi.mocked(apiClient.delete).mockRejectedValue(axiosError);

      await expect(deleteComment(1)).rejects.toThrow(CommentError);
      try {
        await deleteComment(1);
      } catch (error) {
        expect(error).toBeInstanceOf(CommentError);
        expect((error as CommentError).code).toBe('VALIDATION');
        expect((error as CommentError).retryable).toBe(false);
      }
    });

    it('should throw CommentError for 404 Not Found', async () => {
      const axiosError = new AxiosError('Not Found');
      axiosError.response = {
        status: 404,
        statusText: 'Not Found',
        data: {},
        headers: {},
        config: {} as any,
      };

      vi.mocked(apiClient.delete).mockRejectedValue(axiosError);

      await expect(deleteComment(999)).rejects.toThrow(CommentError);
      try {
        await deleteComment(999);
      } catch (error) {
        expect(error).toBeInstanceOf(CommentError);
        expect((error as CommentError).code).toBe('VALIDATION');
        expect((error as CommentError).retryable).toBe(false);
      }
    });
  });

  describe('getReplies', () => {
    it('should get replies successfully', async () => {
      const mockResponse: ReplyListResponse = {
        replies: [
          {
            id: 2,
            track_id: 1,
            creator_id: 123,
            parent_id: 1,
            content: 'Reply 1',
            is_edited: false,
            created_at: '2024-01-01T00:00:00Z',
            updated_at: '2024-01-01T00:00:00Z',
            user: {
              id: 123,
              username: 'testuser',
            },
          },
        ],
        total: 1,
        page: 1,
        limit: 20,
      };

      vi.mocked(apiClient.get).mockResolvedValue({
        data: mockResponse,
        status: 200,
        statusText: 'OK',
        headers: {},
        config: {} as any,
      });

      const result = await getReplies(1);

      expect(result).toEqual(mockResponse);
      expect(apiClient.get).toHaveBeenCalledWith(
        '/comments/1/replies?page=1&limit=20',
      );
    });

    it('should get replies with pagination', async () => {
      const mockResponse: ReplyListResponse = {
        replies: [],
        total: 10,
        page: 2,
        limit: 5,
      };

      vi.mocked(apiClient.get).mockResolvedValue({
        data: mockResponse,
        status: 200,
        statusText: 'OK',
        headers: {},
        config: {} as any,
      });

      const result = await getReplies(1, 2, 5);

      expect(result).toEqual(mockResponse);
      expect(apiClient.get).toHaveBeenCalledWith(
        '/comments/1/replies?page=2&limit=5',
      );
    });

    it('should throw CommentError for 404 Not Found', async () => {
      const axiosError = new AxiosError('Not Found');
      axiosError.response = {
        status: 404,
        statusText: 'Not Found',
        data: {},
        headers: {},
        config: {} as any,
      };

      vi.mocked(apiClient.get).mockRejectedValue(axiosError);

      await expect(getReplies(999)).rejects.toThrow(CommentError);
      try {
        await getReplies(999);
      } catch (error) {
        expect(error).toBeInstanceOf(CommentError);
        expect((error as CommentError).code).toBe('VALIDATION');
        expect((error as CommentError).retryable).toBe(false);
      }
    });

    it('should throw CommentError for network errors', async () => {
      const axiosError = new AxiosError('timeout');
      axiosError.code = 'ECONNABORTED';
      axiosError.response = undefined;

      vi.mocked(apiClient.get).mockRejectedValue(axiosError);

      await expect(getReplies(1)).rejects.toThrow(CommentError);
      try {
        await getReplies(1);
      } catch (error) {
        expect(error).toBeInstanceOf(CommentError);
        expect((error as CommentError).code).toBe('NETWORK');
        expect((error as CommentError).retryable).toBe(true);
      }
    });
  });
});
