You have too many functions, making your code really hard to understand.
You're being too helpful at the cost of maintainability.
You don't need state if state else self.state in most functions.
Just demand the consumer of the function to pass the correct value in.
Your code is WET, you're duplicating code across *_O and *_X functions.
def remove_O(self, ...
COL1 == COL3
This will make it hard to win in one of those two columns!
Your mainline will call board.add_and_update_state(move) with move = 0, which in turn calls various other methods with move = 0.
Functions like is_move_decisive() use statements like move = move if move else self.last_move, which therefore replaces move with self.last_move ...