################################################################################
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
# limitations under the License.
################################################################################

from typing import Callable, Dict, List, Optional

import pyarrow as pa
import pyarrow.compute as pc

from pypaimon.common.predicate_json_parser import (
    extract_referenced_fields,
    parse_predicate_to_batch_filter,
)
from pypaimon.read.plan import Plan
from pypaimon.read.query_auth_split import QueryAuthSplit
from pypaimon.schema.data_types import DataField


class TableQueryAuthResult:

    def __init__(self, filter: Optional[List[str]], column_masking: Optional[Dict[str, str]]):
        self.filter = [f for f in filter if f] if filter else filter
        self.column_masking = (
            {k: v for k, v in column_masking.items() if k and v}
            if column_masking else column_masking
        )

    @property
    def has_restrictions(self):
        return bool(self.filter) or bool(self.column_masking)

    def convert_plan(self, plan):
        if not self.has_restrictions:
            return plan
        auth_splits = [QueryAuthSplit(split, self) for split in plan.splits()]
        return Plan(auth_splits, snapshot_id=plan.snapshot_id)

    def extract_row_filter(self) -> Optional[Callable[[pa.RecordBatch], pa.Array]]:
        if not self.filter:
            return None
        filters = [parse_predicate_to_batch_filter(json_str) for json_str in self.filter]
        if len(filters) == 1:
            return filters[0]

        def combined(batch: pa.RecordBatch) -> pa.Array:
            result = filters[0](batch)
            for f in filters[1:]:
                result = pc.and_(result, f(batch))
            return result
        return combined

    def get_extra_fields_for_filter(
            self,
            read_fields: List[DataField],
            table_fields: List[DataField],
    ) -> List[DataField]:
        if not self.filter:
            return []
        read_field_names = {f.name for f in read_fields}
        extra = []
        for json_str in self.filter:
            referenced = extract_referenced_fields(json_str)
            for name in referenced:
                if name not in read_field_names:
                    field = next((f for f in table_fields if f.name == name), None)
                    if field:
                        extra.append(field)
                        read_field_names.add(name)
        return extra
