import os
from typing import List

from dbt.dataclass_schema import ValidationError

from dbt.contracts.graph.parsed import (
    IntermediateSnapshotNode, ParsedSnapshotNode
)
from dbt.exceptions import (
    CompilationException, validator_error_message
)
from dbt.node_types import NodeType
from dbt.parser.base import SQLParser
from dbt.parser.search import (
    BlockContents, BlockSearcher, FileBlock
)
from dbt.utils import split_path


class SnapshotParser(
    SQLParser[IntermediateSnapshotNode, ParsedSnapshotNode]
):
    def parse_from_dict(self, dct, validate=True) -> IntermediateSnapshotNode:
        if validate:
            IntermediateSnapshotNode.validate(dct)
        return IntermediateSnapshotNode.from_dict(dct)

    @property
    def resource_type(self) -> NodeType:
        return NodeType.Snapshot

    @classmethod
    def get_compiled_path(cls, block: FileBlock):
        return block.path.relative_path

    def set_snapshot_attributes(self, node):
        # use the target_database setting if we got it, otherwise the
        # `database` value of the node (ultimately sourced from the `database`
        # config value), and if that is not set, use the database defined in
        # the adapter's credentials.
        if node.config.target_database:
            node.database = node.config.target_database
        elif not node.database:
            node.database = self.root_project.credentials.database

        # the target schema must be set if we got here, so overwrite the node's
        # schema
        node.schema = node.config.target_schema

        return node

    def get_fqn(self, path: str, name: str) -> List[str]:
        """Get the FQN for the node. This impacts node selection and config
        application.

        On snapshots, the fqn includes the filename.
        """
        no_ext = os.path.splitext(path)[0]
        fqn = [self.project.project_name]
        fqn.extend(split_path(no_ext))
        fqn.append(name)
        return fqn

    def transform(self, node: IntermediateSnapshotNode) -> ParsedSnapshotNode:
        try:
            dct = node.to_dict(omit_none=True)
            parsed_node = ParsedSnapshotNode.from_dict(dct)
            self.set_snapshot_attributes(parsed_node)
            return parsed_node
        except ValidationError as exc:
            raise CompilationException(validator_error_message(exc), node)

    def parse_file(self, file_block: FileBlock) -> None:
        blocks = BlockSearcher(
            source=[file_block],
            allowed_blocks={'snapshot'},
            source_tag_factory=BlockContents,
        )
        for block in blocks:
            self.parse_node(block)
