# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed 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.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========

import orjson

from mirage.accessor.postgres import PostgresAccessor
from mirage.core.postgres import _client

_TEXT_TYPES = (
    "text",
    "character varying",
    "character",
    "name",
    "uuid",
    "json",
    "jsonb",
)


async def _text_columns(conn, schema: str, name: str) -> list[str]:
    rows = await conn.fetch(
        "SELECT column_name FROM information_schema.columns "
        "WHERE table_schema = $1 AND table_name = $2 "
        "AND data_type = ANY($3::text[]) "
        "ORDER BY ordinal_position", schema, name, list(_TEXT_TYPES))
    return [r["column_name"] for r in rows]


async def search_entity(accessor: PostgresAccessor, schema: str, kind: str,
                        entity: str, pattern: str, limit: int) -> list[dict]:
    pool = await accessor.pool()
    async with pool.acquire() as conn:
        cols = await _text_columns(conn, schema, entity)
        if not cols:
            return []
        where = " OR ".join(f'"{c}"::text ILIKE $1' for c in cols)
        sql = f'SELECT * FROM "{schema}"."{entity}" WHERE {where} LIMIT $2'
        rows = await conn.fetch(sql, f"%{pattern}%", limit)
        return [dict(r) for r in rows]


async def search_kind(accessor: PostgresAccessor, schema: str, kind: str,
                      pattern: str,
                      limit: int) -> list[tuple[str, str, str, list[dict]]]:
    pool = await accessor.pool()
    async with pool.acquire() as conn:
        if kind == "tables":
            names = await _client.list_tables(conn, schema)
        else:
            views = await _client.list_views(conn, schema)
            mviews = await _client.list_matviews(conn, schema)
            names = sorted(set(views) | set(mviews))
    out: list[tuple[str, str, str, list[dict]]] = []
    for n in names:
        rows = await search_entity(accessor, schema, kind, n, pattern, limit)
        if rows:
            out.append((schema, kind, n, rows))
    return out


async def search_schema(accessor: PostgresAccessor, schema: str, pattern: str,
                        limit: int) -> list[tuple[str, str, str, list[dict]]]:
    out: list[tuple[str, str, str, list[dict]]] = []
    for kind in ("tables", "views"):
        out.extend(await search_kind(accessor, schema, kind, pattern, limit))
    return out


async def search_database(
        accessor: PostgresAccessor, pattern: str,
        limit: int) -> list[tuple[str, str, str, list[dict]]]:
    pool = await accessor.pool()
    async with pool.acquire() as conn:
        schemas = await _client.list_schemas(conn, accessor.config.schemas)
    out: list[tuple[str, str, str, list[dict]]] = []
    for s in schemas:
        out.extend(await search_schema(accessor, s, pattern, limit))
    return out


def format_grep_results(
        results: list[tuple[str, str, str, list[dict]]]) -> list[str]:
    lines: list[str] = []
    for schema, kind, entity, rows in results:
        for r in rows:
            line = orjson.dumps(r, default=str).decode()
            lines.append(f"{schema}/{kind}/{entity}/rows.jsonl:{line}")
    return lines
