require 'rails_helper'

RSpec.describe 'Materials API', type: :request do
  include ActiveJob::TestHelper

  let!(:member_user) { create(:user, :member) }
  let!(:admin_user)  { create(:user, :admin) }
  let!(:guest_user)  { create(:user) }

  def dummy_upload(filename: 'dummy.png', type: 'image/png', body: 'dummy')
    Rack::Test::UploadedFile.new(StringIO.new(body), type, original_filename: filename)
  end

  def response_materials
    json.fetch('materials')
  end

  def build_material(tag:, user:, parent: nil, file: dummy_upload, url: nil)
    Material.new(tag:, parent:, url:,
                 created_by_user: user,
                 updated_by_user: user).tap do |material|
      material.file.attach(file) if file
      material.save!
    end
  end

  describe 'GET /materials' do
    let!(:tag_a) do
      Tag.create!(tag_name: TagName.create!(name: 'material_index_a'), category: :material)
    end
    let!(:tag_b) do
      Tag.create!(tag_name: TagName.create!(name: 'material_index_b'), category: :material)
    end

    let!(:material_a) do
      build_material(tag: tag_a, user: member_user, file: dummy_upload(filename: 'a.png'))
    end

    let!(:material_b) do
      build_material(tag: tag_b, user: member_user, parent: material_a,
                     file: dummy_upload(filename: 'b.png'))
    end

    before do
      old_time = Time.zone.local(2026, 3, 29, 1, 0, 0)
      new_time = Time.zone.local(2026, 3, 29, 2, 0, 0)

      material_a.update_columns(created_at: old_time, updated_at: old_time)
      material_b.update_columns(created_at: new_time, updated_at: new_time)
    end

    it 'returns materials with count and metadata' do
      get '/materials'

      expect(response).to have_http_status(:ok)
      expect(json).to include('materials', 'count')
      expect(response_materials).to be_an(Array)
      expect(json['count']).to eq(2)

      row = response_materials.find { |m| m['id'] == material_b.id }
      expect(row).to be_present
      expect(row['tag']).to include(
        'id' => tag_b.id,
        'name' => 'material_index_b',
        'category' => 'material'
      )
      expect(row['content_type']).to eq('image/png')
      expect(row['media_kind']).to eq('image')
    end

    it 'filters materials by q' do
      get '/materials', params: { q: 'material_index_a' }

      expect(response).to have_http_status(:ok)
      expect(json['count']).to eq(1)
      expect(response_materials.map { |m| m['id'] }).to eq([material_a.id])
    end

    it 'filters materials by tag_state' do
      untagged =
          build_material(tag: nil, user: member_user,
                         file: dummy_upload(filename: 'untagged.png'))

      get '/materials', params: { tag_state: 'untagged' }

      expect(response).to have_http_status(:ok)
      expect(json['count']).to eq(1)
      expect(response_materials.map { |m| m['id'] }).to eq([untagged.id])
    end

    it 'filters materials by media_kind' do
      get '/materials', params: { media_kind: 'image' }

      expect(response).to have_http_status(:ok)
      expect(json['count']).to eq(2)
      expect(response_materials.map { |m| m['media_kind'] }).to all(eq('image'))
    end

    it 'paginates and keeps total count' do
      get '/materials', params: { page: 2, limit: 1 }

      expect(response).to have_http_status(:ok)
      expect(json['count']).to eq(2)
      expect(response_materials.size).to eq(1)
      expect(response_materials.first['id']).to eq(material_a.id)
    end

    it 'normalises invalid page and limit' do
      get '/materials', params: { page: 0, limit: 0 }

      expect(response).to have_http_status(:ok)
      expect(json['count']).to eq(2)
      expect(response_materials.size).to eq(1)
      expect(response_materials.first['id']).to eq(material_b.id)
    end

    it 'filters by descendant tags and returns stable parent tag groups' do
      root =
          Tag.create!(tag_name: TagName.create!(name: 'material_scope_root'),
                      category: :material)
      child_b =
          Tag.create!(tag_name: TagName.create!(name: 'material_scope_b'),
                      category: :material)
      child_a =
          Tag.create!(tag_name: TagName.create!(name: 'material_scope_a'),
                      category: :material)
      deprecated =
          Tag.create!(tag_name: TagName.create!(name: 'material_scope_old'),
                      category: :material,
                      deprecated_at: Time.current)
      grandchild =
          Tag.create!(tag_name: TagName.create!(name: 'material_scope_grandchild'),
                      category: :material)
      root_material =
          build_material(tag: root, user: member_user,
                         file: dummy_upload(filename: 'root.png'))
      child_a_material =
          build_material(tag: child_a, user: member_user,
                         file: dummy_upload(filename: 'child_a.png'))
      grandchild_material =
          build_material(tag: grandchild, user: member_user,
                         file: dummy_upload(filename: 'grandchild.png'))

      TagImplication.create!(parent_tag: root, tag: child_b)
      TagImplication.create!(parent_tag: root, tag: child_a)
      TagImplication.create!(parent_tag: child_b, tag: deprecated)
      TagImplication.create!(parent_tag: deprecated, tag: grandchild)

      get '/materials',
          params: { tag_id: root.id, include_descendants: '1',
                    group_by: 'parent_tag', sort: 'id', direction: 'asc' }

      expect(response).to have_http_status(:ok)
      expect(response_materials.map { |m| m['id'] })
        .to include(root_material.id, child_a_material.id, grandchild_material.id)
      expect(json['tag_scope']).to eq(
        'tag' => {
          'id' => root.id,
          'name' => 'material_scope_root',
          'category' => 'material'
        },
        'include_descendants' => true
      )
      expect(json['groups'].map { |group| group.dig('tag', 'name') })
        .to eq(['material_scope_a', 'material_scope_b', 'material_scope_root'])
      expect(json['groups'].find { |group| group.dig('tag', 'id') == child_b.id }
        .fetch('material_ids')).to eq([grandchild_material.id])
    end

    it 'returns nil tag_scope when tag_id is unknown' do
      get '/materials', params: { tag_id: 999_999, group_by: 'parent_tag' }

      expect(response).to have_http_status(:ok)
      expect(json['tag_scope']).to be_nil
      expect(json['groups']).to eq([])
      expect(response_materials).to eq([])
    end
  end

  describe 'GET /materials/:id' do
    let!(:tag) do
      Tag.create!(tag_name: TagName.create!(name: 'material_show'), category: :material)
    end
    let!(:material) do
      build_material(tag:, user: member_user, file: dummy_upload(filename: 'show.png'))
    end

    it 'returns a material with file, tag, and content_type' do
      get "/materials/#{ material.id }"

      expect(response).to have_http_status(:ok)
      expect(json).to include(
        'id' => material.id,
        'content_type' => 'image/png'
      )
      expect(json['file']).to be_present
      expect(json['tag']).to include(
        'id' => tag.id,
        'name' => 'material_show',
        'category' => 'material'
      )
    end

    it 'returns 404 when material does not exist' do
      get '/materials/999999999'
      expect(response).to have_http_status(:not_found)
    end
  end

  describe 'POST /materials' do
    context 'when not logged in' do
      before { sign_out }

      it 'returns 401' do
        post '/materials', params: {
          tag: 'material_create_unauthorized',
          file: dummy_upload
        }

        expect(response).to have_http_status(:unauthorized)
      end
    end

    context 'when logged in but not member' do
      before { sign_in_as(guest_user) }

      it 'returns 403' do
        post '/materials', params: {
          tag: 'material_create_guest_forbidden',
          file: dummy_upload
        }

        expect(response).to have_http_status(:forbidden)
      end
    end

    context 'when member' do
      before { sign_in_as(member_user) }

      it 'returns 422 when tag is blank' do
        post '/materials', params: { tag: '   ', file: dummy_upload }

        expect(response).to have_http_status(:unprocessable_entity)
        expect(json.fetch('errors')).to include(
            'tag' => ['タグは必須です．'])
      end

      it 'returns 422 when both file and url are blank' do
        post '/materials', params: { tag: 'material_create_blank' }

        expect(response).to have_http_status(:unprocessable_entity)
        expect(json.fetch('errors')).to include(
            'file' => ['ファイルまたは URL は必須です．'],
            'url' => ['ファイルまたは URL は必須です．'])
      end

      it 'creates a material with an attached file' do
        expect do
          post '/materials', params: {
            tag: 'material_create_new',
            file: dummy_upload(filename: 'created.png'),
            export_paths: { legacy_drive: '伊地知ニジカ/created.png' }
          }
        end.to change(Material, :count).by(1)
          .and change(Tag, :count).by(1)
          .and change(TagName, :count).by(1)
          .and change(MaterialVersion, :count).by(1)

        expect(response).to have_http_status(:created)

        material = Material.order(:id).last
        expect(material.tag.name).to eq('material_create_new')
        expect(material.tag.category).to eq('material')
        expect(material.created_by_user).to eq(member_user)
        expect(material.updated_by_user).to eq(member_user)
        expect(material.file.attached?).to be(true)
        expect(material.version_no).to eq(1)
        expect(material.material_versions.first.event_type).to eq('create')
        expect(material.material_export_items.first.export_path).to eq('伊地知ニジカ/created.png')
        expect(material.material_versions.first.export_paths_json).to eq(
          'legacy_drive' => '伊地知ニジカ/created.png'
        )

        expect(json['id']).to eq(material.id)
        expect(json.dig('tag', 'name')).to eq('material_create_new')
        expect(json['content_type']).to eq('image/png')
        expect(json.dig('export_paths', 'legacy_drive')).to eq('伊地知ニジカ/created.png')
      end

      it 'creates a create tag_version for a newly created material tag' do
        expect do
          post '/materials', params: {
            tag: 'material_create_versioned_tag',
            file: dummy_upload(filename: 'created.png')
          }
        end.to change(TagVersion, :count).by(1)

        expect(response).to have_http_status(:created)

        tag = Tag.joins(:tag_name).find_by!(tag_names: { name: 'material_create_versioned_tag' })
        version = tag.tag_versions.order(:version_no).last

        expect(version.event_type).to eq('create')
        expect(version.name).to eq('material_create_versioned_tag')
        expect(version.category).to eq('material')
        expect(version.created_by_user).to eq(member_user)
      end

      it 'snapshots attached file metadata and sha256' do
        post '/materials', params: {
          tag: 'material_create_file_version',
          file: dummy_upload(filename: 'created.png', body: 'sha-body')
        }

        expect(response).to have_http_status(:created)

        version = Material.order(:id).last.material_versions.first
        expect(version.file_blob_id).to be_present
        expect(version.file_filename).to eq('created.png')
        expect(version.file_content_type).to eq('image/png')
        expect(version.file_byte_size).to eq('sha-body'.bytesize)
        expect(version.file_sha256).to eq(Digest::SHA256.hexdigest('sha-body'))
      end

      it 'returns 422 when the existing tag is not material/character' do
        general_tag_name = TagName.create!(name: 'material_create_general_tag')
        Tag.create!(tag_name: general_tag_name, category: :general)

        post '/materials', params: {
          tag: 'material_create_general_tag',
          file: dummy_upload
        }

        expect(response).to have_http_status(:unprocessable_entity)
      end

      it 'persists url-only material' do
        expect do
          post '/materials', params: {
            tag: 'material_create_url_only',
            url: 'https://example.com/material-source'
          }
        end.to change(Material, :count).by(1)

        expect(response).to have_http_status(:created)

        material = Material.order(:id).last
        expect(material.tag.name).to eq('material_create_url_only')
        expect(material.url).to eq('https://example.com/material-source')
        expect(material.file.attached?).to be(false)
      end

      it 'returns the original url for url-only material' do
        post '/materials', params: {
          tag: 'material_create_url_only_response',
          url: 'https://example.com/material-source'
        }

        expect(response).to have_http_status(:created)
        expect(json['url']).to eq('https://example.com/material-source')
      end

      it 'rejects sha256-blocked file upload' do
        sha256 = Digest::SHA256.hexdigest('blocked-body')
        MaterialImportBlock.create!(match_kind: 'sha256',
                                    sha256:,
                                    reason: 'copyright_high_risk',
                                    created_by_user: admin_user)

        expect do
          post '/materials', params: {
            tag: 'material_blocked_create',
            file: dummy_upload(filename: 'blocked.png', body: 'blocked-body')
          }
        end.not_to change(Material, :count)

        expect(response).to have_http_status(:unprocessable_entity)
        expect(json.fetch('errors')).to include(
          'file' => ['抑止された素材です: copyright_high_risk']
        )
      end
    end
  end

  describe 'PUT /materials/:id' do
    let!(:tag) do
      Tag.create!(tag_name: TagName.create!(name: 'material_update_old'), category: :material)
    end
    let!(:material) do
      build_material(tag:, user: member_user, file: dummy_upload(filename: 'old.png'))
    end

    context 'when not logged in' do
      before { sign_out }

      it 'returns 401' do
        put "/materials/#{ material.id }", params: {
          tag: 'material_update_new',
          file: dummy_upload(filename: 'new.png')
        }

        expect(response).to have_http_status(:unauthorized)
      end
    end

    context 'when logged in but not member' do
      before { sign_in_as(guest_user) }

      it 'returns 403' do
        put "/materials/#{ material.id }", params: {
          tag: 'material_update_new',
          file: dummy_upload(filename: 'new.png')
        }

        expect(response).to have_http_status(:forbidden)
      end
    end

    context 'when member' do
      before { sign_in_as(member_user) }

      it 'returns 404 when material does not exist' do
        put '/materials/999999999', params: {
          tag: 'material_update_missing',
          file: dummy_upload
        }

        expect(response).to have_http_status(:not_found)
      end

      it 'returns 422 when tag is blank' do
        put "/materials/#{ material.id }", params: {
          tag: '   ',
          file: dummy_upload
        }

        expect(response).to have_http_status(:unprocessable_entity)
        expect(json.fetch('errors')).to include(
            'tag' => ['タグは必須です．'])
      end

      it 'keeps the existing file when file and url are omitted' do
        put "/materials/#{ material.id }", params: {
          tag: 'material_update_no_payload'
        }

        expect(response).to have_http_status(:ok)
        expect(material.reload.file.attached?).to be(true)
      end

      it 'updates tag, url, file, and updated_by_user' do
        old_blob_id = material.file.blob.id

        expect do
          put "/materials/#{ material.id }", params: {
            tag: 'material_update_new',
            url: 'https://example.com/updated-source',
            file: dummy_upload(filename: 'updated.jpg', type: 'image/jpeg'),
            export_paths: { legacy_drive: '伊地知ニジカ/updated.jpg' }
          }
        end.to change(MaterialVersion, :count).by(2)

        expect(response).to have_http_status(:ok)

        material.reload
        expect(material.tag.name).to eq('material_update_new')
        expect(material.tag.category).to eq('material')
        expect(material.url).to eq('https://example.com/updated-source')
        expect(material.updated_by_user).to eq(member_user)
        expect(material.file.attached?).to be(true)
        expect(material.file.blob.id).not_to eq(old_blob_id)
        expect(ActiveStorage::Blob.where(id: old_blob_id).exists?).to be(true)
        expect(material.file.blob.filename.to_s).to eq('updated.jpg')
        expect(material.file.blob.content_type).to eq('image/jpeg')
        expect(material.version_no).to eq(2)
        expect(material.material_versions.order(:version_no).last.event_type).to eq('update')
        expect(material.material_export_items.first.export_path).to eq('伊地知ニジカ/updated.jpg')
        expect(material.material_versions.order(:version_no).last.export_paths_json).to eq(
          'legacy_drive' => '伊地知ニジカ/updated.jpg'
        )

        expect(json['id']).to eq(material.id)
        expect(json['file']).to be_present
        expect(json['content_type']).to eq('image/jpeg')
        expect(json.dig('tag', 'name')).to eq('material_update_new')
      end

      it 'creates a create tag_version when update creates a new tag' do
        expect do
          put "/materials/#{ material.id }", params: {
            tag: 'material_update_versioned_tag',
            file: dummy_upload(filename: 'updated.png')
          }
        end.to change(Tag, :count).by(1)
          .and change(TagName, :count).by(1)
          .and change(TagVersion, :count).by(1)

        expect(response).to have_http_status(:ok)

        tag = Tag.joins(:tag_name).find_by!(tag_names: { name: 'material_update_versioned_tag' })
        version = tag.tag_versions.order(:version_no).last

        expect(version.event_type).to eq('create')
        expect(version.name).to eq('material_update_versioned_tag')
        expect(version.category).to eq('material')
        expect(version.created_by_user).to eq(member_user)
      end

      it 'backfills a create tag_version for an existing material tag without history' do
        existing_tag =
            Tag.create!(tag_name: TagName.create!(name: 'material_update_existing_no_history'),
                        category: :material)

        expect(existing_tag.tag_versions).to be_empty

        expect do
          put "/materials/#{ material.id }", params: {
            tag: 'material_update_existing_no_history',
            file: dummy_upload(filename: 'updated.png')
          }
        end.to change(TagVersion, :count).by(1)

        expect(response).to have_http_status(:ok)

        version = existing_tag.reload.tag_versions.order(:version_no).last
        expect(version.event_type).to eq('create')
        expect(version.name).to eq('material_update_existing_no_history')
        expect(version.category).to eq('material')
        expect(version.created_by_user).to eq(member_user)
      end

      it 'backfills a create tag_version for an existing character tag without history' do
        existing_tag =
            Tag.create!(tag_name: TagName.create!(name: 'material_update_character_no_history'),
                        category: :character)

        expect(existing_tag.tag_versions).to be_empty

        expect do
          put "/materials/#{ material.id }", params: {
            tag: 'material_update_character_no_history',
            file: dummy_upload(filename: 'updated.png')
          }
        end.to change(TagVersion, :count).by(1)

        expect(response).to have_http_status(:ok)

        version = existing_tag.reload.tag_versions.order(:version_no).last
        expect(version.event_type).to eq('create')
        expect(version.name).to eq('material_update_character_no_history')
        expect(version.category).to eq('character')
        expect(version.created_by_user).to eq(member_user)
      end

      it 'detaches the existing file without purging blob when url replaces file' do
        old_blob_id = material.file.blob.id

        put "/materials/#{ material.id }", params: {
          tag: 'material_update_remove_file',
          url: 'https://example.com/updated-source'
        }

        expect(response).to have_http_status(:ok)

        material.reload
        expect(material.tag.name).to eq('material_update_remove_file')
        expect(material.url).to eq('https://example.com/updated-source')
        expect(material.updated_by_user).to eq(member_user)
        expect(material.file.attached?).to be(false)

        expect(ActiveStorage::Blob.where(id: old_blob_id).exists?).to be(true)

        expect(json['id']).to eq(material.id)
        expect(json['file']).to be_nil
        expect(json['content_type']).to be_nil
        expect(json.dig('tag', 'name')).to eq('material_update_remove_file')
        expect(json['url']).to eq('https://example.com/updated-source')
      end

      it 'does not increase version for the same snapshot update' do
        MaterialVersionRecorder.record!(material:, event_type: :create,
                                        created_by_user: member_user)
        TagVersioning.ensure_snapshot!(tag, created_by_user: member_user)

        expect do
          put "/materials/#{ material.id }", params: {
            tag: 'material_update_old'
          }
        end.not_to change(MaterialVersion, :count)

        expect(tag.reload.tag_versions.count).to eq(1)

        expect(response).to have_http_status(:ok)
        expect(material.reload.version_no).to eq(1)
      end

      it 'records update version when only export_path changes' do
        MaterialVersionRecorder.record!(material:, event_type: :create,
                                        created_by_user: member_user)

        expect do
          put "/materials/#{ material.id }", params: {
            tag: 'material_update_old',
            export_paths: { legacy_drive: '素材/only-path.png' }
          }
        end.to change(MaterialVersion, :count).by(1)

        expect(response).to have_http_status(:ok)
        expect(material.reload.material_export_items.first.export_path).to eq('素材/only-path.png')
        expect(material.material_versions.order(:version_no).last.export_paths_json).to eq(
          'legacy_drive' => '素材/only-path.png'
        )
      end

      it 'removes export_path item when blank is submitted' do
        MaterialExportItem.create!(material:, profile: 'legacy_drive',
                                   export_path: '素材/remove.png',
                                   created_by_user: member_user)
        MaterialVersionRecorder.record!(material:, event_type: :create,
                                        created_by_user: member_user)

        expect do
          put "/materials/#{ material.id }", params: {
            tag: 'material_update_old',
            export_paths: { legacy_drive: '' }
          }
        end.to change(MaterialExportItem, :count).by(-1)
          .and change(MaterialVersion, :count).by(1)

        expect(response).to have_http_status(:ok)
        expect(material.reload.material_export_items).to be_empty
        expect(material.material_versions.order(:version_no).last.export_paths_json).to eq({})
      end

      it 'rejects sha256-blocked replacement file' do
        sha256 = Digest::SHA256.hexdigest('blocked-update')
        MaterialImportBlock.create!(match_kind: 'sha256',
                                    sha256:,
                                    reason: 'source_owner_request',
                                    created_by_user: admin_user)

        put "/materials/#{ material.id }", params: {
          tag: 'material_update_old',
          file: dummy_upload(filename: 'blocked.png', body: 'blocked-update')
        }

        expect(response).to have_http_status(:unprocessable_entity)
        expect(material.reload.file.blob.filename.to_s).to eq('old.png')
      end
    end
  end

  describe 'GET /materials/download.zip' do
    let!(:tag_a) { Tag.create!(tag_name: TagName.create!(name: 'zip_a'), category: :material) }
    let!(:tag_b) { Tag.create!(tag_name: TagName.create!(name: 'zip_b'), category: :material) }
    let!(:material_a) do
      build_material(tag: tag_a, user: member_user,
                     file: dummy_upload(filename: 'a.png', body: 'zip-a'))
    end
    let!(:material_b) do
      build_material(tag: tag_b, user: member_user,
                     file: dummy_upload(filename: 'b.png', body: 'zip-b'))
    end

    before do
      MaterialExportItem.create!(material: material_a, profile: 'legacy_drive',
                                 export_path: '素材/a.png',
                                 created_by_user: member_user)
      MaterialExportItem.create!(material: material_b, profile: 'legacy_drive',
                                 export_path: '素材/b.png',
                                 created_by_user: member_user)
    end

    it 'uses material_export_items.export_path as ZIP entry paths' do
      get '/materials/download.zip', params: { profile: 'legacy_drive' }

      expect(response).to have_http_status(:ok)
      expect(response.media_type).to eq('application/zip')
      expect(response.body.b).to include('素材/a.png'.b)
      expect(response.body.b).to include('素材/b.png'.b)
    end

    it 'filters by tag_id' do
      get '/materials/download.zip', params: { profile: 'legacy_drive', tag_id: tag_a.id }

      expect(response).to have_http_status(:ok)
      expect(response.body.b).to include('素材/a.png'.b)
      expect(response.body.b).not_to include('素材/b.png'.b)
    end

    it 'does not include disabled export items' do
      material_b.material_export_items.first.update!(enabled: false)

      get '/materials/download.zip', params: { profile: 'legacy_drive' }

      expect(response).to have_http_status(:ok)
      expect(response.body.b).to include('素材/a.png'.b)
      expect(response.body.b).not_to include('素材/b.png'.b)
    end
  end

  describe 'GET /materials/versions' do
    let!(:tag) do
      Tag.create!(tag_name: TagName.create!(name: 'material_history'), category: :material)
    end
    let!(:material) do
      build_material(tag:, user: member_user, file: dummy_upload(filename: 'history.png'))
    end

    it 'returns material versions in reverse chronological order' do
      MaterialVersionRecorder.record!(material:, event_type: :create,
                                      created_by_user: member_user)
      material.update!(url: 'https://example.com/history')
      MaterialVersionRecorder.record!(material:, event_type: :update,
                                      created_by_user: admin_user)

      get '/materials/versions', params: { material_id: material.id }

      expect(response).to have_http_status(:ok)
      expect(json['count']).to eq(2)
      versions = json.fetch('versions')
      expect(versions.map { |v| v['version_no'] }).to eq([2, 1])
      expect(versions.first).to include(
        'material_id' => material.id,
        'event_type' => 'update',
        'tag_name' => 'material_history',
        'file_filename' => 'history.png',
        'url' => 'https://example.com/history'
      )
      expect(versions.first['created_by_user']).to include('id' => admin_user.id)
    end

    it 'filters material versions by tag and event_type' do
      MaterialVersionRecorder.record!(material:, event_type: :create,
                                      created_by_user: member_user)

      get '/materials/versions', params: {
        tag: 'material_history',
        event_type: 'create'
      }

      expect(response).to have_http_status(:ok)
      expect(json.fetch('versions').map { |v| v['material_id'] }).to include(material.id)
      expect(json.fetch('versions').map { |v| v['event_type'] }).to all(eq('create'))
    end
  end

  describe 'material sync suppressions' do
    it 'requires member permission for index' do
      sign_out

      get '/materials/suppressions'

      expect(response).to have_http_status(:unauthorized)
    end

    it 'creates source suppression as member' do
      sign_in_as(member_user)

      expect do
        post '/materials/suppressions', params: {
          source_kind: 'google_drive_path_prefix',
          drive_path: '伊地知ニジカ/危険素材',
          reason: 'copyright_high_risk'
        }
      end.to change(MaterialSyncSuppression, :count).by(1)

      expect(response).to have_http_status(:created)
      expect(json).to include(
        'source_kind' => 'google_drive_path_prefix',
        'drive_path' => '伊地知ニジカ/危険素材',
        'normalized_source_key' => 'google_drive_path_prefix:伊地知ニジカ/危険素材'
      )
    end
  end

  describe 'DELETE /materials/:id' do
    let!(:tag) do
      Tag.create!(tag_name: TagName.create!(name: 'material_destroy'), category: :material)
    end
    let!(:material) do
      build_material(tag:, user: member_user, file: dummy_upload(filename: 'destroy.png'))
    end

    context 'when not logged in' do
      before { sign_out }

      it 'returns 401' do
        delete "/materials/#{ material.id }"
        expect(response).to have_http_status(:unauthorized)
      end
    end

    context 'when logged in but not member' do
      before { sign_in_as(guest_user) }

      it 'returns 403' do
        delete "/materials/#{ material.id }"
        expect(response).to have_http_status(:forbidden)
      end
    end

    context 'when member' do
      before { sign_in_as(member_user) }

      it 'returns 404 when material does not exist' do
        delete '/materials/999999999'
        expect(response).to have_http_status(:not_found)
      end

      it 'discards the material and returns 204' do
        delete "/materials/#{ material.id }"

        expect(response).to have_http_status(:no_content)
        expect(Material.find_by(id: material.id)).to be_nil
        expect(Material.with_discarded.find(material.id)).to be_discarded
      end
    end
  end
end
