import {
  ModerationOpts,
  interpretLabelValueDefinition,
  mock,
  moderatePost,
  moderateProfile,
} from '../src/index.js'
import './util/moderation-behavior.js'

describe('Moderation', () => {
  it('Applies self-labels on profiles according to the global preferences', () => {
    // porn (hide)
    const res1 = moderateProfile(
      mock.profileViewBasic({
        handle: 'bob.test',
        displayName: 'Bob',
        labels: [
          {
            src: 'did:web:bob.test',
            uri: 'at://did:web:bob.test/app.bsky.actor.profile/self',
            val: 'porn',
            cts: new Date().toISOString(),
          },
        ],
      }),
      {
        userDid: 'did:web:alice.test',
        prefs: {
          adultContentEnabled: true,
          labels: {
            porn: 'hide',
          },
          labelers: [],
          hiddenPosts: [],
          mutedWords: [],
        },
      },
    )
    expect(res1.ui('avatar')).toBeModerationResult(
      ['blur'],
      'post avatar',
      JSON.stringify(res1, null, 2),
      true,
    )

    // porn (ignore)
    const res2 = moderateProfile(
      mock.profileViewBasic({
        handle: 'bob.test',
        displayName: 'Bob',
        labels: [
          {
            src: 'did:web:bob.test',
            uri: 'at://did:web:bob.test/app.bsky.actor.profile/self',
            val: 'porn',
            cts: new Date().toISOString(),
          },
        ],
      }),
      {
        userDid: 'did:web:alice.test',
        prefs: {
          adultContentEnabled: true,
          labels: {
            porn: 'ignore',
          },
          labelers: [],
          hiddenPosts: [],
          mutedWords: [],
        },
      },
    )
    expect(res2.ui('avatar')).toBeModerationResult(
      [],
      'post avatar',
      JSON.stringify(res1, null, 2),
      true,
    )
  })

  it('Ignores labels from unsubscribed moderators or ignored labels for a moderator', () => {
    // porn (moderator disabled)
    const res1 = moderateProfile(
      mock.profileViewBasic({
        handle: 'bob.test',
        displayName: 'Bob',
        labels: [
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.actor.profile/self',
            val: 'porn',
            cts: new Date().toISOString(),
          },
        ],
      }),
      {
        userDid: 'did:web:alice.test',
        prefs: {
          adultContentEnabled: true,
          labels: {
            porn: 'hide',
          },
          labelers: [],
          hiddenPosts: [],
          mutedWords: [],
        },
      },
    )
    for (const k of [
      'profileList',
      'profileView',
      'avatar',
      'banner',
      'displayName',
      'contentList',
      'contentView',
      'contentMedia',
    ] as const) {
      expect(res1.ui(k)).toBeModerationResult(
        [],
        k,
        JSON.stringify(res1, null, 2),
      )
    }

    // porn (label group disabled)
    const res2 = moderateProfile(
      mock.profileViewBasic({
        handle: 'bob.test',
        displayName: 'Bob',
        labels: [
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.actor.profile/self',
            val: 'porn',
            cts: new Date().toISOString(),
          },
        ],
      }),
      {
        userDid: 'did:web:alice.test',
        prefs: {
          adultContentEnabled: true,
          labels: {
            porn: 'ignore',
          },
          labelers: [
            {
              did: 'did:web:labeler.test',
              labels: { porn: 'ignore' },
            },
          ],
          hiddenPosts: [],
          mutedWords: [],
        },
      },
    )
    for (const k of [
      'profileList',
      'profileView',
      'avatar',
      'banner',
      'displayName',
      'contentList',
      'contentView',
      'contentMedia',
    ] as const) {
      expect(res2.ui(k)).toBeModerationResult(
        [],
        k,
        JSON.stringify(res2, null, 2),
      )
    }
  })

  it('Can manually apply hiding', () => {
    const res1 = moderatePost(
      mock.postView({
        record: {
          $type: 'app.bsky.feed.post',
          text: 'Hello',
          createdAt: new Date().toISOString(),
        },
        author: mock.profileViewBasic({
          handle: 'bob.test',
          displayName: 'Bob',
        }),
        labels: [],
      }),
      {
        userDid: 'did:web:alice.test',
        prefs: {
          adultContentEnabled: true,
          labels: {},
          labelers: [
            {
              did: 'did:web:labeler.test',
              labels: {},
            },
          ],
          hiddenPosts: [],
          mutedWords: [],
        },
      },
    )
    res1.addHidden(true)
    expect(res1.ui('contentList')).toBeModerationResult(
      ['filter', 'blur'],
      'contentList',
    )
    expect(res1.ui('contentView')).toBeModerationResult(['blur'], 'contentView')
  })

  it('Prioritizes filters and blurs correctly on merge', () => {
    const res1 = moderatePost(
      mock.postView({
        record: {
          $type: 'app.bsky.feed.post',
          text: 'Hello',
          createdAt: new Date().toISOString(),
        },
        author: mock.profileViewBasic({
          handle: 'bob.test',
          displayName: 'Bob',
        }),
        labels: [
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.post/fake',
            val: 'porn',
            cts: new Date().toISOString(),
          },
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.post/fake',
            val: '!hide',
            cts: new Date().toISOString(),
          },
        ],
      }),
      {
        userDid: 'did:web:alice.test',
        prefs: {
          adultContentEnabled: true,
          labels: {
            porn: 'hide',
          },
          labelers: [
            {
              did: 'did:web:labeler.test',
              labels: {},
            },
          ],
          hiddenPosts: [],
          mutedWords: [],
        },
      },
    )
    expect((res1.ui('contentList').filters[0] as any).label.val).toBe('!hide')
    expect((res1.ui('contentList').filters[1] as any).label.val).toBe('porn')
    expect((res1.ui('contentList').blurs[0] as any).label.val).toBe('!hide')
    expect((res1.ui('contentMedia').blurs[0] as any).label.val).toBe('porn')
  })

  it('Prioritizes custom label definitions', () => {
    const modOpts: ModerationOpts = {
      userDid: 'did:web:alice.test',
      prefs: {
        adultContentEnabled: true,
        labels: { porn: 'warn' },
        labelers: [
          {
            did: 'did:web:labeler.test',
            labels: { porn: 'warn' },
          },
        ],
        hiddenPosts: [],
        mutedWords: [],
      },
      labelDefs: {
        'did:web:labeler.test': [
          interpretLabelValueDefinition(
            {
              identifier: 'porn',
              blurs: 'none',
              severity: 'inform',
              locales: [],
              defaultSetting: 'warn',
            },
            'did:web:labeler.test',
          ),
        ],
      },
    }
    const res = moderatePost(
      mock.postView({
        record: {
          $type: 'app.bsky.feed.post',
          text: 'Hello',
          createdAt: new Date().toISOString(),
        },
        author: mock.profileViewBasic({
          handle: 'bob.test',
          displayName: 'Bob',
        }),
        labels: [
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.post/fake',
            val: 'porn',
            cts: new Date().toISOString(),
          },
        ],
      }),
      modOpts,
    )
    expect(res.ui('profileList')).toBeModerationResult([])
    expect(res.ui('profileView')).toBeModerationResult([])
    expect(res.ui('avatar')).toBeModerationResult([])
    expect(res.ui('banner')).toBeModerationResult([])
    expect(res.ui('displayName')).toBeModerationResult([])
    expect(res.ui('contentList')).toBeModerationResult(['inform'])
    expect(res.ui('contentView')).toBeModerationResult(['inform'])
    expect(res.ui('contentMedia')).toBeModerationResult([])
  })

  it('Doesnt allow custom behaviors to override imperative labels', () => {
    const modOpts: ModerationOpts = {
      userDid: 'did:web:alice.test',
      prefs: {
        adultContentEnabled: true,
        labels: {},
        labelers: [
          {
            did: 'did:web:labeler.test',
            labels: {},
          },
        ],
        hiddenPosts: [],
        mutedWords: [],
      },
      labelDefs: {
        'did:web:labeler.test': [
          interpretLabelValueDefinition(
            {
              identifier: '!hide',
              blurs: 'none',
              severity: 'inform',
              locales: [],
              defaultSetting: 'warn',
            },
            'did:web:labeler.test',
          ),
        ],
      },
    }
    const res = moderatePost(
      mock.postView({
        record: {
          $type: 'app.bsky.feed.post',
          text: 'Hello',
          createdAt: new Date().toISOString(),
        },
        author: mock.profileViewBasic({
          handle: 'bob.test',
          displayName: 'Bob',
        }),
        labels: [
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.post/fake',
            val: '!hide',
            cts: new Date().toISOString(),
          },
        ],
      }),
      modOpts,
    )

    expect(res.ui('profileList')).toBeModerationResult([])
    expect(res.ui('profileView')).toBeModerationResult([])
    expect(res.ui('avatar')).toBeModerationResult([])
    expect(res.ui('banner')).toBeModerationResult([])
    expect(res.ui('displayName')).toBeModerationResult([])
    expect(res.ui('contentList')).toBeModerationResult([
      'filter',
      'blur',
      'noOverride',
    ])
    expect(res.ui('contentView')).toBeModerationResult(['blur', 'noOverride'])
    expect(res.ui('contentMedia')).toBeModerationResult([])
  })

  it('Ignores invalid label value names', () => {
    const modOpts: ModerationOpts = {
      userDid: 'did:web:alice.test',
      prefs: {
        adultContentEnabled: true,
        labels: {},
        labelers: [
          {
            did: 'did:web:labeler.test',
            labels: { BadLabel: 'hide', 'bad/label': 'hide' },
          },
        ],
        hiddenPosts: [],
        mutedWords: [],
      },
      labelDefs: {
        'did:web:labeler.test': [
          interpretLabelValueDefinition(
            {
              identifier: 'BadLabel',
              blurs: 'content',
              severity: 'inform',
              locales: [],
              defaultSetting: 'warn',
            },
            'did:web:labeler.test',
          ),
          interpretLabelValueDefinition(
            {
              identifier: 'bad/label',
              blurs: 'content',
              severity: 'inform',
              locales: [],
              defaultSetting: 'warn',
            },
            'did:web:labeler.test',
          ),
        ],
      },
    }
    const res = moderatePost(
      mock.postView({
        record: {
          $type: 'app.bsky.feed.post',
          text: 'Hello',
          createdAt: new Date().toISOString(),
        },
        author: mock.profileViewBasic({
          handle: 'bob.test',
          displayName: 'Bob',
        }),
        labels: [
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.post/fake',
            val: 'BadLabel',
            cts: new Date().toISOString(),
          },
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.post/fake',
            val: 'bad/label',
            cts: new Date().toISOString(),
          },
        ],
      }),
      modOpts,
    )

    expect(res.ui('profileList')).toBeModerationResult([])
    expect(res.ui('profileView')).toBeModerationResult([])
    expect(res.ui('avatar')).toBeModerationResult([])
    expect(res.ui('banner')).toBeModerationResult([])
    expect(res.ui('displayName')).toBeModerationResult([])
    expect(res.ui('contentList')).toBeModerationResult([])
    expect(res.ui('contentView')).toBeModerationResult([])
    expect(res.ui('contentMedia')).toBeModerationResult([])
  })

  it('Custom labels can set the default setting', () => {
    const modOpts: ModerationOpts = {
      userDid: 'did:web:alice.test',
      prefs: {
        adultContentEnabled: true,
        labels: {},
        labelers: [
          {
            did: 'did:web:labeler.test',
            labels: {},
          },
        ],
        hiddenPosts: [],
        mutedWords: [],
      },
      labelDefs: {
        'did:web:labeler.test': [
          interpretLabelValueDefinition(
            {
              identifier: 'default-hide',
              blurs: 'content',
              severity: 'inform',
              defaultSetting: 'hide',
              locales: [],
            },
            'did:web:labeler.test',
          ),
          interpretLabelValueDefinition(
            {
              identifier: 'default-warn',
              blurs: 'content',
              severity: 'inform',
              defaultSetting: 'warn',
              locales: [],
            },
            'did:web:labeler.test',
          ),
          interpretLabelValueDefinition(
            {
              identifier: 'default-ignore',
              blurs: 'content',
              severity: 'inform',
              defaultSetting: 'ignore',
              locales: [],
            },
            'did:web:labeler.test',
          ),
        ],
      },
    }
    const res1 = moderatePost(
      mock.postView({
        record: {
          $type: 'app.bsky.feed.post',
          text: 'Hello',
          createdAt: new Date().toISOString(),
        },
        author: mock.profileViewBasic({
          handle: 'bob.test',
          displayName: 'Bob',
        }),
        labels: [
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.post/fake',
            val: 'default-hide',
            cts: new Date().toISOString(),
          },
        ],
      }),
      modOpts,
    )

    expect(res1.ui('profileList')).toBeModerationResult([])
    expect(res1.ui('profileView')).toBeModerationResult([])
    expect(res1.ui('avatar')).toBeModerationResult([])
    expect(res1.ui('banner')).toBeModerationResult([])
    expect(res1.ui('displayName')).toBeModerationResult([])
    expect(res1.ui('contentList')).toBeModerationResult(['filter', 'blur'])
    expect(res1.ui('contentView')).toBeModerationResult(['inform'])
    expect(res1.ui('contentMedia')).toBeModerationResult([])

    const res2 = moderatePost(
      mock.postView({
        record: {
          $type: 'app.bsky.feed.post',
          text: 'Hello',
          createdAt: new Date().toISOString(),
        },
        author: mock.profileViewBasic({
          handle: 'bob.test',
          displayName: 'Bob',
        }),
        labels: [
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.post/fake',
            val: 'default-warn',
            cts: new Date().toISOString(),
          },
        ],
      }),
      modOpts,
    )

    expect(res2.ui('profileList')).toBeModerationResult([])
    expect(res2.ui('profileView')).toBeModerationResult([])
    expect(res2.ui('avatar')).toBeModerationResult([])
    expect(res2.ui('banner')).toBeModerationResult([])
    expect(res2.ui('displayName')).toBeModerationResult([])
    expect(res2.ui('contentList')).toBeModerationResult(['blur'])
    expect(res2.ui('contentView')).toBeModerationResult(['inform'])
    expect(res2.ui('contentMedia')).toBeModerationResult([])

    const res3 = moderatePost(
      mock.postView({
        record: {
          $type: 'app.bsky.feed.post',
          text: 'Hello',
          createdAt: new Date().toISOString(),
        },
        author: mock.profileViewBasic({
          handle: 'bob.test',
          displayName: 'Bob',
        }),
        labels: [
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.post/fake',
            val: 'default-ignore',
            cts: new Date().toISOString(),
          },
        ],
      }),
      modOpts,
    )

    expect(res3.ui('profileList')).toBeModerationResult([])
    expect(res3.ui('profileView')).toBeModerationResult([])
    expect(res3.ui('avatar')).toBeModerationResult([])
    expect(res3.ui('banner')).toBeModerationResult([])
    expect(res3.ui('displayName')).toBeModerationResult([])
    expect(res3.ui('contentList')).toBeModerationResult([])
    expect(res3.ui('contentView')).toBeModerationResult([])
    expect(res3.ui('contentMedia')).toBeModerationResult([])
  })

  it('Custom labels can require adult content to be enabled', () => {
    const modOpts: ModerationOpts = {
      userDid: 'did:web:alice.test',
      prefs: {
        adultContentEnabled: false,
        labels: { adult: 'ignore' },
        labelers: [
          {
            did: 'did:web:labeler.test',
            labels: {
              adult: 'ignore',
            },
          },
        ],
        hiddenPosts: [],
        mutedWords: [],
      },
      labelDefs: {
        'did:web:labeler.test': [
          interpretLabelValueDefinition(
            {
              identifier: 'adult',
              blurs: 'content',
              severity: 'inform',
              defaultSetting: 'hide',
              adultOnly: true,
              locales: [],
            },
            'did:web:labeler.test',
          ),
        ],
      },
    }
    const res = moderatePost(
      mock.postView({
        record: {
          $type: 'app.bsky.feed.post',
          text: 'Hello',
          createdAt: new Date().toISOString(),
        },
        author: mock.profileViewBasic({
          handle: 'bob.test',
          displayName: 'Bob',
        }),
        labels: [
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.post/fake',
            val: 'adult',
            cts: new Date().toISOString(),
          },
        ],
      }),
      modOpts,
    )

    expect(res.ui('profileList')).toBeModerationResult([])
    expect(res.ui('profileView')).toBeModerationResult([])
    expect(res.ui('avatar')).toBeModerationResult([])
    expect(res.ui('banner')).toBeModerationResult([])
    expect(res.ui('displayName')).toBeModerationResult([])
    expect(res.ui('contentList')).toBeModerationResult([
      'filter',
      'blur',
      'noOverride',
    ])
    expect(res.ui('contentView')).toBeModerationResult(['blur', 'noOverride'])
    expect(res.ui('contentMedia')).toBeModerationResult([])
  })

  it('Adult content disabled forces the preference to hide', () => {
    const modOpts: ModerationOpts = {
      userDid: 'did:web:alice.test',
      prefs: {
        adultContentEnabled: false,
        labels: { porn: 'ignore' },
        labelers: [
          {
            did: 'did:web:labeler.test',
            labels: {},
          },
        ],
        hiddenPosts: [],
        mutedWords: [],
      },
      labelDefs: {},
    }
    const res = moderatePost(
      mock.postView({
        record: {
          $type: 'app.bsky.feed.post',
          text: 'Hello',
          createdAt: new Date().toISOString(),
        },
        author: mock.profileViewBasic({
          handle: 'bob.test',
          displayName: 'Bob',
        }),
        labels: [
          {
            src: 'did:web:labeler.test',
            uri: 'at://did:web:bob.test/app.bsky.post/fake',
            val: 'porn',
            cts: new Date().toISOString(),
          },
        ],
      }),
      modOpts,
    )

    expect(res.ui('profileList')).toBeModerationResult([])
    expect(res.ui('profileView')).toBeModerationResult([])
    expect(res.ui('avatar')).toBeModerationResult([])
    expect(res.ui('banner')).toBeModerationResult([])
    expect(res.ui('displayName')).toBeModerationResult([])
    expect(res.ui('contentList')).toBeModerationResult(['filter'])
    expect(res.ui('contentView')).toBeModerationResult([])
    expect(res.ui('contentMedia')).toBeModerationResult(['blur', 'noOverride'])
  })
})
