It's possible that you've got data mixed between SSTables. Do you have read repairs enabled or potentially mix your writes by issuing updates? If so, you may have timestamp overlaps in your SSTables which will cause Cassandra to block dropping expired tables. You can check if this is the case with sstableexpiredblockers.
https://cassandra.apache.org/doc/stable/cassandra/tools/sstable/sstableexpiredblockers.html
You can also directly check for overlap between your SSTables by comparing the min and max timestamps. Check this post from The Last Pickle that uses sstablemetadata.
https://thelastpickle.com/blog/2016/12/08/TWCS-part1.html
As mentioned by Andrew, you can have Cassandra ignore the overlap by setting unsafe_aggressive_sstable_expiration
which will delete old tables once they expire. I've found this very helpful for managing TWCS tables, but it can cause deleted data to reappear, so make sure you fully understand it before enabling.