import { expect } from 'chai'
import sinon from 'sinon'
import Path from 'node:path'
import fs from 'node:fs'
import _ from 'lodash'
import User from './helpers/User.mjs'
import UserHelper from './helpers/UserHelper.mjs'
import MockDocstoreApiClass from './mocks/MockDocstoreApi.mjs'
import MockFilestoreApiClass from './mocks/MockFilestoreApi.mjs'
import MockV1HistoryApiClass from './mocks/MockV1HistoryApi.mjs'
import { fileURLToPath } from 'node:url'
import Features from '../../../app/src/infrastructure/Features.js'

let MockDocstoreApi, MockFilestoreApi, MockV1HistoryApi

const __dirname = fileURLToPath(new URL('.', import.meta.url))

before(function () {
  MockDocstoreApi = MockDocstoreApiClass.instance()
  MockFilestoreApi = MockFilestoreApiClass.instance()
  MockV1HistoryApi = MockV1HistoryApiClass.instance()
})

describe('ProjectDuplicateNames', function () {
  beforeEach(function (done) {
    this.owner = new User()
    this.owner.login(done)
    this.project = {}
    this.callback = sinon.stub()
  })

  describe('creating a project from the example template', function () {
    beforeEach(function (done) {
      this.owner.createProject(
        'example-project',
        { template: 'example' },
        (error, projectId) => {
          expect(error).to.not.exist
          this.example_project_id = projectId
          this.owner.getProject(projectId, (error, project) => {
            expect(error).to.not.exist
            this.project = project
            this.mainTexDoc = _.find(
              project.rootFolder[0].docs,
              doc => doc.name === 'main.tex'
            )
            this.refBibDoc = _.find(
              project.rootFolder[0].docs,
              doc => doc.name === 'sample.bib'
            )
            this.imageFile = _.find(
              project.rootFolder[0].fileRefs,
              file => file.name === 'frog.jpg'
            )
            this.rootFolderId = project.rootFolder[0]._id.toString()
            // create a folder called 'testfolder'
            this.owner.request.post(
              {
                uri: `/project/${this.example_project_id}/folder`,
                json: {
                  name: 'testfolder',
                  parent_folder_id: this.rootFolderId,
                },
              },
              (err, res, body) => {
                expect(err).to.not.exist
                this.testFolderId = body._id
                done()
              }
            )
          })
        }
      )
    })

    it('should create a project', function () {
      expect(this.project.rootFolder[0].docs.length).to.equal(2)
      expect(this.project.rootFolder[0].fileRefs.length).to.equal(1)
    })

    it('should create two docs in the docstore', function () {
      const docs = MockDocstoreApi.docs[this.example_project_id]
      expect(Object.keys(docs).length).to.equal(2)
    })

    if (Features.hasFeature('project-history-blobs')) {
      it('should create one file in the history-v1', function () {
        const files =
          MockV1HistoryApi.blobs[this.project.overleaf.history.id.toString()]
        expect(Object.keys(files).length).to.equal(1)
      })
    }
    if (Features.hasFeature('filestore')) {
      it('should create one file in the filestore', function () {
        const files = MockFilestoreApi.files[this.example_project_id]
        expect(Object.keys(files).length).to.equal(1)
      })
    }

    describe('for an existing doc', function () {
      describe('trying to add a doc with the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/doc`,
              json: {
                name: 'main.tex',
                parent_folder_id: this.rootFolderId,
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to add a folder with the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/folder`,
              json: {
                name: 'main.tex',
                parent_folder_id: this.rootFolderId,
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })
    })

    describe('for an existing file', function () {
      describe('trying to add a doc with the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/doc`,
              json: {
                name: 'frog.jpg',
                parent_folder_id: this.rootFolderId,
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to add a folder with the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/folder`,
              json: {
                name: 'frog.jpg',
                parent_folder_id: this.rootFolderId,
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to upload a file with the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/upload`,
              json: true,
              qs: {
                folder_id: this.rootFolderId,
                qqfilename: 'frog.jpg',
              },
              formData: {
                name: 'frog.jpg',
                qqfile: {
                  value: fs.createReadStream(
                    Path.join(__dirname, '/../files/1pixel.png')
                  ),
                  options: {
                    filename: 'frog.jpg',
                    contentType: 'image/jpeg',
                  },
                },
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.body = body
              // update the image id because we have replaced the file
              this.imageFile._id = this.body.entity_id
              done()
            }
          )
        })

        it('should succeed (overwriting the file)', function () {
          expect(this.body.success).to.equal(true)
        })
      })
    })
    // at this point the @imageFile._id has changed

    describe('for an existing folder', function () {
      describe('trying to add a doc with the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/doc`,
              json: {
                name: 'testfolder',
                parent_folder_id: this.rootFolderId,
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to add a folder with the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/folder`,
              json: {
                name: 'testfolder',
                parent_folder_id: this.rootFolderId,
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to upload a file with the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/upload`,
              json: true,
              qs: {
                folder_id: this.rootFolderId,
                qqfilename: 'frog.jpg',
              },
              formData: {
                qqfile: {
                  value: fs.createReadStream(
                    Path.join(__dirname, '/../files/1pixel.png')
                  ),
                  options: {
                    filename: 'testfolder',
                    contentType: 'image/jpeg',
                  },
                },
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.body = body
              done()
            }
          )
        })

        it('should respond with failure status', function () {
          expect(this.body.success).to.equal(false)
        })
      })
    })

    describe('rename for an existing doc', function () {
      describe('trying to rename a doc to the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/doc/${this.refBibDoc._id}/rename`,
              json: {
                name: 'main.tex',
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to rename a folder to the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/folder/${this.testFolderId}/rename`,
              json: {
                name: 'main.tex',
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to rename a file to the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/file/${this.imageFile._id}/rename`,
              json: {
                name: 'main.tex',
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with failure status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })
    })

    describe('rename for an existing file', function () {
      describe('trying to rename a doc to the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/doc/${this.refBibDoc._id}/rename`,
              json: {
                name: 'frog.jpg',
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to rename a folder to the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/folder/${this.testFolderId}/rename`,
              json: {
                name: 'frog.jpg',
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to rename a file to the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/file/${this.imageFile._id}/rename`,
              json: {
                name: 'frog.jpg',
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with failure status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })
    })

    describe('rename for an existing folder', function () {
      describe('trying to rename a doc to the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/doc/${this.refBibDoc._id}/rename`,
              json: {
                name: 'testfolder',
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to rename a folder to the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/folder/${this.testFolderId}/rename`,
              json: {
                name: 'testfolder',
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to rename a file to the same name', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/file/${this.imageFile._id}/rename`,
              json: {
                name: 'testfolder',
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with failure status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })
    })

    describe('for an existing folder with a file with the same name', function () {
      beforeEach(function (done) {
        this.owner.request.post(
          {
            uri: `/project/${this.example_project_id}/doc`,
            json: {
              name: 'main.tex',
              parent_folder_id: this.testFolderId,
            },
          },
          (err, res, body) => {
            if (err) {
              throw err
            }
            this.owner.request.post(
              {
                uri: `/project/${this.example_project_id}/doc`,
                json: {
                  name: 'frog.jpg',
                  parent_folder_id: this.testFolderId,
                },
              },
              (err, res, body) => {
                if (err) {
                  throw err
                }
                this.owner.request.post(
                  {
                    uri: `/project/${this.example_project_id}/folder`,
                    json: {
                      name: 'otherFolder',
                      parent_folder_id: this.testFolderId,
                    },
                  },
                  (err, res, body) => {
                    if (err) {
                      throw err
                    }
                    this.subFolderId = body._id
                    this.owner.request.post(
                      {
                        uri: `/project/${this.example_project_id}/folder`,
                        json: {
                          name: 'otherFolder',
                          parent_folder_id: this.rootFolderId,
                        },
                      },
                      (err, res, body) => {
                        if (err) {
                          throw err
                        }
                        this.otherFolderId = body._id
                        done()
                      }
                    )
                  }
                )
              }
            )
          }
        )
      })

      describe('trying to move a doc into the folder', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/doc/${this.mainTexDoc._id}/move`,
              json: {
                folder_id: this.testFolderId,
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to move a file into the folder', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/file/${this.imageFile._id}/move`,
              json: {
                folder_id: this.testFolderId,
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to move a folder into the folder', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/folder/${this.otherFolderId}/move`,
              json: {
                folder_id: this.testFolderId,
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })

      describe('trying to move a folder into a subfolder of itself', function () {
        beforeEach(function (done) {
          this.owner.request.post(
            {
              uri: `/project/${this.example_project_id}/folder/${this.testFolderId}/move`,
              json: {
                folder_id: this.subFolderId,
              },
            },
            (err, res, body) => {
              expect(err).to.not.exist
              this.res = res
              done()
            }
          )
        })

        it('should respond with 400 error status', function () {
          expect(this.res.statusCode).to.equal(400)
        })
      })
    })
  })

  describe('regex characters in title', function () {
    let response, userHelper
    beforeEach(async function () {
      userHelper = new UserHelper()
      userHelper = await UserHelper.createUser()
      userHelper = await UserHelper.loginUser(
        userHelper.getDefaultEmailPassword()
      )
    })
    it('should handle characters that would cause an invalid regular expression', async function () {
      const projectName = 'Example (test'
      response = await userHelper.fetch('/project/new', {
        method: 'POST',
        body: new URLSearchParams([['projectName', projectName]]),
      })
      const body = await response.json()
      expect(response.status).to.equal(200) // can create project
      response = await userHelper.fetch(`/project/${body.project_id}`)
      expect(response.status).to.equal(200) // can open project
    })
  })
})
