# pylint: disable=unused-argument,invalid-name,line-too-long
from __future__ import annotations

from typing import List, Optional

from parse import parse
from sqlalchemy import text as sql_text

from alembic_utils.exceptions import SQLParseFailure
from alembic_utils.replaceable_entity import ReplaceableEntity


class PGView(ReplaceableEntity):
    """ A PostgreSQL View that can be versioned and replaced """

    @classmethod
    def from_sql(cls, sql: str) -> Optional[PGView]:
        """Create an instance of PGFunction from a blob of sql"""
        templates = [
            "create{}view{:s}{schema}.{signature}{:s}as{:s}{definition}",
        ]
        for template in templates:
            result = parse(template, sql, case_sensitive=False)
            if result is not None:
                return cls(
                    schema=result["schema"],
                    signature=result["signature"],
                    definition=result["definition"],
                )

        raise SQLParseFailure(sql)

    def to_sql_statement_create(self) -> str:
        """Generates a SQL "create view" statement"""
        return sql_text(f"CREATE VIEW {self.schema}.{self.signature} AS {self.definition}")

    def to_sql_statement_drop(self) -> str:
        """Generates a SQL "drop view" statement"""
        return sql_text(f"DROP VIEW {self.schema}.{self.signature}")

    def to_sql_statement_create_or_replace(self) -> str:
        """Generates a SQL "create or replace view" statement"""
        return sql_text(f"CREATE OR REPLACE VIEW {self.schema}.{self.signature} AS {self.definition}")

    @classmethod
    def from_database(cls, connection, schema="%") -> List[PGView]:
        """Get a list of all functions defined in the db"""
        sql = sql_text(
            f"""
        select
            schemaname schema_name,
            viewname view_name,
            pg_get_viewdef(view_oid, true) as definition_pretty
        from
            pg_views,
            lateral (
                select (schemaname || '.' || viewname)::regclass::oid view_oid
            ) abc
        where
            schemaname not in ('pg_catalog', 'information_schema')
            and schemaname like '{schema}';
        """
        )
        rows = connection.execute(sql).fetchall()
        db_views = [PGView(x[0], x[1], x[2]) for x in rows]

        for view in db_views:
            assert view is not None

        return db_views

    def get_compare_identity_query(self) -> str:
        """Return SQL string that returns 1 row for existing DB object"""
        return f"""
        select
            -- Schema is appended in python
            viewname view_name
        from
            pg_views
        where
            schemaname like '{self.schema}';
        """

    def get_compare_definition_query(self) -> str:
        """Return SQL string that returns 1 row for existing DB object"""
        return f"""
        select
            -- Schema is appended in python
            viewname view_name,
            pg_get_viewdef((schemaname || '.' || viewname)::regclass::oid, true) definition
        from
	    pg_views
	where
            schemaname like '{self.schema}';
        """
