import type { BunWorkspaceMeta, CheckOptions } from '../src/types'
import process from 'node:process'
import { afterEach, beforeEach, describe, expect, it, vi, vitest } from 'vitest'
import { CheckPackages } from '../src'
import * as bunWorkspaces from '../src/io/bunWorkspaces'

// output that should be written to the package.json file
let output: string | undefined
vi.mock('node:fs/promises', async (importActual) => {
  return {
    ...await importActual(),
    writeFile(_path: string, data: string) {
      output = data.toString()
      return Promise.resolve()
    },
  }
})

vitest.mock('@antfu/ni', () => ({
  detect: vitest.fn().mockResolvedValue({ agent: 'npm', version: '1.0.0' }),
}))

vitest.mock('package-manager-detector', () => ({
  detect: vitest.fn().mockResolvedValue({ agent: 'npm', version: '1.0.0' }),
}))

vitest.mock('../src/utils/npm.ts', () => ({
  parseNpmConfig: vitest.fn().mockResolvedValue({}),
}))

vitest.mock('../src/io/resolves.ts', async (importOriginal) => {
  const actual = await importOriginal()
  return {
    ...actual as any,
    resolveDependency: vitest.fn().mockResolvedValue({
      name: 'react',
      currentVersion: '^18.2.0',
      source: 'bun-workspace',
      targetVersion: '^19.2.4',
      diff: null,
      resolveError: null,
      update: false,
      pkgData: {},
    }),
  }
})

beforeEach(() => {
  output = undefined
})

afterEach(() => {
  vitest.restoreAllMocks()
})

describe('bun catalog integration', () => {
  it('should detect and process bun catalogs when bun.lockb exists', async () => {
    const options: CheckOptions = {
      cwd: `${process.cwd()}/test/fixtures/bun-catalog`,
    }
    const result = await CheckPackages(options, {})

    expect(result.packages.map(p => p.name)).toMatchInlineSnapshot(`
      [
        "bun-catalog:default",
        "bun-catalog:react18",
        "bun-catalog:react19",
        "@taze/bun-monorepo-example",
      ]
    `)

    expect(
      result.packages.flatMap(p => ({
        name: p.name,
        packages: p.resolved
          .map(r => [r.name, r.targetVersion])
          .sort((a, b) => a[0].localeCompare(b[0])),
      })),
    ).toMatchInlineSnapshot(`
      [
        {
          "name": "bun-catalog:default",
          "packages": [
            [
              "react",
              "^19.2.4",
            ],
            [
              "react-dom",
              "^19.2.4",
            ],
          ],
        },
        {
          "name": "bun-catalog:react18",
          "packages": [
            [
              "react",
              "^18.2.0",
            ],
            [
              "react-dom",
              "^18.2.0",
            ],
          ],
        },
        {
          "name": "bun-catalog:react19",
          "packages": [
            [
              "react",
              "^19.2.4",
            ],
            [
              "react-dom",
              "^19.2.4",
            ],
          ],
        },
        {
          "name": "@taze/bun-monorepo-example",
          "packages": [
            [
              "@types/lodash",
              "^4.14.0",
            ],
            [
              "express",
              "4.12.x",
            ],
            [
              "lodash",
              "^4.13.19",
            ],
            [
              "multer",
              "^0.1.8",
            ],
            [
              "react-bootstrap",
              "^0.22.6",
            ],
            [
              "typescript",
              "3.5",
            ],
            [
              "webpack",
              "~1.9.10",
            ],
          ],
        },
      ]
    `)
  })
})

describe('bun catalog write functionality', () => {
  it('should update default catalog in package.json', async () => {
    const packageJsonRaw = {
      name: '@test/bun-workspace',
      workspaces: {
        catalog: {
          'react': '^19.2.4',
          'react-dom': '^19.2.4',
        },
        catalogs: {
          react18: {
            'react': '^18.2.0',
            'react-dom': '^18.2.0',
          },
        },
      },
    }

    const pkg: BunWorkspaceMeta = {
      name: 'bun-catalog:default',
      resolved: [
        // testing purpose - simulate updated versions
        { name: 'react', targetVersion: '^19.2.4', source: 'bun-workspace', update: true, currentVersion: '^19.0.0', diff: 'minor' } as any,
        { name: 'react-dom', targetVersion: '^19.2.4', source: 'bun-workspace', update: true, currentVersion: '^19.0.0', diff: 'minor' } as any,
      ],
      raw: packageJsonRaw,
      filepath: '/tmp/package.json',
      type: 'bun-workspace',
      private: false,
      version: '',
      relative: '',
      deps: [],
    }

    await bunWorkspaces.writeBunWorkspace(pkg, {})

    expect(output).toContain('"react": "^19.2.4"')
    expect(output).toContain('"react-dom": "^19.2.4"')

    // Verify the JSON structure is maintained
    const writtenJson = JSON.parse(output!)
    expect(writtenJson.workspaces.catalog.react).toBe('^19.2.4')
    expect(writtenJson.workspaces.catalog['react-dom']).toBe('^19.2.4')

    // Ensure other catalogs are preserved
    expect(writtenJson.workspaces.catalogs.react18.react).toBe('^18.2.0')
  })

  it('should update named catalog in package.json', async () => {
    const packageJsonRaw = {
      name: '@test/bun-workspace',
      workspaces: {
        catalog: {
          'react': '^19.2.4',
          'react-dom': '^19.2.4',
        },
        catalogs: {
          react18: {
            'react': '^18.2.0',
            'react-dom': '^18.2.0',
          },
          react19: {
            'react': '^19.2.4',
            'react-dom': '^19.2.4',
          },
        },
      },
    }

    const pkg: BunWorkspaceMeta = {
      name: 'bun-catalog:react19',
      resolved: [
        // testing purpose - simulate updated versions
        { name: 'react', targetVersion: '^19.2.4', source: 'bun-workspace', update: true, currentVersion: '^18.2.0', diff: 'patch' } as any,
        { name: 'react-dom', targetVersion: '^19.2.4', source: 'bun-workspace', update: true, currentVersion: '^18.2.0', diff: 'patch' } as any,
      ],
      raw: packageJsonRaw,
      filepath: '/tmp/package.json',
      type: 'bun-workspace',
      private: false,
      version: '',
      relative: '',
      deps: [],
    }

    await bunWorkspaces.writeBunWorkspace(pkg, {})

    // Verify the JSON structure is updated correctly
    const writtenJson = JSON.parse(output!)
    expect(writtenJson.workspaces.catalogs.react18.react).toBe('^18.2.0')
    expect(writtenJson.workspaces.catalogs.react18['react-dom']).toBe('^18.2.0')

    // Ensure default catalog and other catalogs are preserved
    expect(writtenJson.workspaces.catalog.react).toBe('^19.2.4')
    expect(writtenJson.workspaces.catalogs.react18.react).toBe('^18.2.0')
  })

  it('should handle empty updates gracefully', async () => {
    const packageJsonRaw = {
      name: '@test/bun-workspace',
      workspaces: {
        catalog: {
          react: '^19.2.4',
        },
      },
    }

    const pkg: BunWorkspaceMeta = {
      name: 'bun-catalog:default',
      resolved: [], // No updates
      raw: packageJsonRaw,
      filepath: '/tmp/package.json',
      type: 'bun-workspace',
      private: false,
      version: '',
      relative: '',
      deps: [],
    }

    await bunWorkspaces.writeBunWorkspace(pkg, {})

    // Should not write anything when there are no updates
    expect(output).toBeUndefined()
  })

  it('should use detect-indent to preserve original file formatting', async () => {
    // Mock readFile to return 4-space indented JSON (simulating project with 4-space indent)
    const fourSpaceJson = JSON.stringify({
      name: '@test/bun-workspace',
      workspaces: {
        catalog: {
          react: '^19.2.4',
        },
      },
    }, null, 4)

    const readFileSpy = vi.spyOn(await import('node:fs/promises'), 'readFile')
    readFileSpy.mockResolvedValueOnce(fourSpaceJson as any)

    const packageJsonRaw = {
      name: '@test/bun-workspace',
      workspaces: {
        catalog: {
          react: '^19.2.4',
        },
      },
    }

    const pkg: BunWorkspaceMeta = {
      name: 'bun-catalog:default',
      resolved: [
        { name: 'react', targetVersion: '^20.0.0', source: 'bun-workspace', update: true, currentVersion: '^19.2.4', diff: 'major' } as any,
      ],
      raw: packageJsonRaw,
      filepath: '/tmp/package.json',
      type: 'bun-workspace',
      private: false,
      version: '',
      relative: '',
      deps: [],
    }

    await bunWorkspaces.writeBunWorkspace(pkg, {})

    // Output should use 4-space indent (matching the original file)
    expect(output).toBeDefined()
    expect(output).toContain('    "name"')
    // Should NOT use 2-space indent
    expect(output).not.toMatch(/^ {2}"name"/m)
  })

  it('should fall back to 2-space indent when file cannot be read', async () => {
    const packageJsonRaw = {
      name: '@test/bun-workspace',
      workspaces: {
        catalog: {
          react: '^19.2.4',
        },
      },
    }

    const pkg: BunWorkspaceMeta = {
      name: 'bun-catalog:default',
      resolved: [
        { name: 'react', targetVersion: '^20.0.0', source: 'bun-workspace', update: true, currentVersion: '^19.2.4', diff: 'major' } as any,
      ],
      raw: packageJsonRaw,
      filepath: '/tmp/nonexistent/package.json',
      type: 'bun-workspace',
      private: false,
      version: '',
      relative: '',
      deps: [],
    }

    await bunWorkspaces.writeBunWorkspace(pkg, {})

    // Should fall back to 2-space indent
    expect(output).toBeDefined()
    expect(output).toContain('  "name"')
  })
})
