require 'cgi'
require 'rails_helper'


RSpec.describe 'Tags API', type: :request do
  let!(:tn) { TagName.create!(name: 'spec_tag') }
  let!(:tag) { Tag.create!(tag_name: tn, category: :general) }
  let!(:alias_tn) { TagName.create!(name: 'unko', canonical: tn) }
  let!(:post) { Post.create!(url: 'https://example.com/unkounkounko') }
  let!(:post_tag) { PostTag.create!(post:, tag:) }
  let!(:tn2) { TagName.create!(name: 'unknown') }
  let!(:tag2) { Tag.create!(tag_name: tn2, category: :general) }

  describe 'GET /tags' do
    it 'returns tags with name' do
      get '/tags'

      expect(response).to have_http_status(:ok)

      expect(json).to be_an(Array)
      expect(json).not_to be_empty
      expect(json[0]).to have_key('name')
      expect(json.map { |t| t['name'] }).to include('spec_tag')
    end
  end

  describe 'GET /tags/:id' do
    subject(:request) do
      get "/tags/#{ tag_id }"
    end

    let(:tag_id) { tag.id }

    context 'when tag exists' do
      it 'returns tag with name' do
        request
        expect(response).to have_http_status(:ok)

        expect(json).to include(
            'id'        => tag.id,
            'name'      => 'spec_tag',
            'category'  => 'general')
      end
    end

    context 'when tag does not exist' do
      let(:tag_id) { 9_999_999 }

      it 'returns 404' do
        request
        expect(response).to have_http_status(:not_found)
      end
    end
  end

  describe 'GET /tags/autocomplete' do
    it 'returns matching tags by q' do
      get '/tags/autocomplete', params: { q: 'spec' }

      expect(response).to have_http_status(:ok)

      expect(json).to be_an(Array)
      expect(json.map { |t| t['name'] }).to include('spec_tag')
      t = json.find { |t| t['name'] == 'spec_tag' }
      expect(t).to have_key('matched_alias')
      expect(t['matched_alias']).to be(nil)
    end

    it 'returns matching canonical tags by q with aliases' do
      get '/tags/autocomplete', params: { q: 'unk' }

      expect(response).to have_http_status(:ok)

      expect(json).to be_an(Array)
      expect(json.map { |t| t['name'] }).to include('spec_tag')
      t = json.find { |t| t['name'] == 'spec_tag' }
      expect(t['matched_alias']).to eq('unko')
      expect(json.map { |t| t['name'] }).not_to include('unknown')
    end
  end

  describe 'GET /tags/name/:name' do
    it 'returns tag by name' do
      get "/tags/name/#{ CGI.escape('spec_tag') }"

      expect(response).to have_http_status(:ok)

      expect(json).to have_key('id')
      expect(json).to have_key('name')
      expect(json['id']).to eq(tag.id)
      expect(json['name']).to eq('spec_tag')
    end

    it 'returns 404 when not found' do
      get "/tags/name/#{ CGI.escape('nope') }"
      expect(response).to have_http_status(:not_found)
    end
  end

  # member? を持つ user を想定（Factory 側で trait 作ってもOK）
  let(:member_user) { create(:user) }
  let(:non_member_user) { create(:user) }

  def stub_current_user(user)
    allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user)
  end

  before do
    allow(member_user).to receive(:member?).and_return(true)
    allow(non_member_user).to receive(:member?).and_return(false)
  end

  describe "PATCH /tags/:id" do
    context "未ログイン" do
      before { stub_current_user(nil) }

      it "401 を返す" do
        patch "/tags/#{tag.id}", params: { name: "new" }
        expect(response).to have_http_status(:unauthorized)
      end
    end

    context "ログインしてゐるが member でない" do
      before { stub_current_user(non_member_user) }

      it "403 を返す" do
        patch "/tags/#{tag.id}", params: { name: "new" }
        expect(response).to have_http_status(:forbidden)
      end
    end

    context "member" do
      before { stub_current_user(member_user) }

      it "name だけ更新できる" do
        patch "/tags/#{tag.id}", params: { name: "new" }

        expect(response).to have_http_status(:ok)

        tag.reload
        expect(tag.name).to eq("new")
        expect(tag.category).to eq("general")

        json = JSON.parse(response.body)
        expect(json["id"]).to eq(tag.id)
        expect(json["name"]).to eq("new")
        expect(json["category"]).to eq("general")
      end

      it "category だけ更新できる" do
        patch "/tags/#{tag.id}", params: { category: "meme" }

        expect(response).to have_http_status(:ok)

        tag.reload
        expect(tag.name).to eq("spec_tag")
        expect(tag.category).to eq("meme")
      end

      it "空文字は presence により無視され、更新は走らない（値が変わらない）" do
        patch "/tags/#{tag.id}", params: { name: "", category: "   " }

        expect(response).to have_http_status(:ok)

        tag.reload
        expect(tag.name).to eq("spec_tag")
        expect(tag.category).to eq("general")
      end

      it "両方更新できる" do
        patch "/tags/#{tag.id}", params: { name: "n", category: "meta" }

        expect(response).to have_http_status(:ok)

        tag.reload
        expect(tag.name).to eq("n")
        expect(tag.category).to eq("meta")
      end

      it "存在しない id だと RecordNotFound になる（通常は 404）" do
        # Rails 設定次第で例外がそのまま上がる/404になる
        # APIなら rescue_from で 404 にしてることが多いので、その場合は 404 を期待。
        patch "/tags/999999999", params: { name: "x" }

        expect(response.status).to be_in([404, 500])
      end

      it "バリデーションで update! が失敗したら（通常は 422 か 500）" do
        patch "/tags/#{tag.id}", params: { name: 'new', category: 'nico' }

        # rescue_from の実装次第で変はる:
        # - RecordInvalid を 422 にしてるなら 422
        # - 未処理なら 500
        expect(response.status).to be_in([422, 500])
      end
    end
  end
end
