# Copyright 2021 The Matrix.org Foundation C.I.C.
#
# 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.

import argparse
import sys
import time
from datetime import datetime
from typing import List

import attr

from synapse.config._base import (
    Config,
    RootConfig,
    find_config_files,
    read_config_files,
)
from synapse.config.database import DatabaseConfig
from synapse.storage.database import DatabasePool, LoggingTransaction, make_conn
from synapse.storage.engines import create_engine


class ReviewConfig(RootConfig):
    "A config class that just pulls out the database config"
    config_classes = [DatabaseConfig]


@attr.s(auto_attribs=True)
class UserInfo:
    user_id: str
    creation_ts: int
    emails: List[str] = attr.Factory(list)
    private_rooms: List[str] = attr.Factory(list)
    public_rooms: List[str] = attr.Factory(list)
    ips: List[str] = attr.Factory(list)


def get_recent_users(
    txn: LoggingTransaction, since_ms: int, exclude_app_service: bool
) -> List[UserInfo]:
    """Fetches recently registered users and some info on them."""

    sql = """
        SELECT name, creation_ts FROM users
        WHERE
            ? <= creation_ts
            AND deactivated = 0
    """

    if exclude_app_service:
        sql += " AND appservice_id IS NULL"

    txn.execute(sql, (since_ms / 1000,))

    user_infos = [UserInfo(user_id, creation_ts) for user_id, creation_ts in txn]

    for user_info in user_infos:
        user_info.emails = DatabasePool.simple_select_onecol_txn(
            txn,
            table="user_threepids",
            keyvalues={"user_id": user_info.user_id, "medium": "email"},
            retcol="address",
        )

        sql = """
            SELECT room_id, canonical_alias, name, join_rules
            FROM local_current_membership
            INNER JOIN room_stats_state USING (room_id)
            WHERE user_id = ? AND membership = 'join'
        """

        txn.execute(sql, (user_info.user_id,))
        for room_id, canonical_alias, name, join_rules in txn:
            if join_rules == "public":
                user_info.public_rooms.append(canonical_alias or name or room_id)
            else:
                user_info.private_rooms.append(canonical_alias or name or room_id)

        user_info.ips = DatabasePool.simple_select_onecol_txn(
            txn,
            table="user_ips",
            keyvalues={"user_id": user_info.user_id},
            retcol="ip",
        )

    return user_infos


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-c",
        "--config-path",
        action="append",
        metavar="CONFIG_FILE",
        help="The config files for Synapse.",
        required=True,
    )
    parser.add_argument(
        "-s",
        "--since",
        metavar="duration",
        help="Specify how far back to review user registrations for, defaults to 7d (i.e. 7 days).",
        default="7d",
    )
    parser.add_argument(
        "-e",
        "--exclude-emails",
        action="store_true",
        help="Exclude users that have validated email addresses.",
    )
    parser.add_argument(
        "-u",
        "--only-users",
        action="store_true",
        help="Only print user IDs that match.",
    )
    parser.add_argument(
        "-a",
        "--exclude-app-service",
        help="Exclude appservice users.",
        action="store_true",
    )

    config = ReviewConfig()

    config_args = parser.parse_args(sys.argv[1:])
    config_files = find_config_files(search_paths=config_args.config_path)
    config_dict = read_config_files(config_files)
    config.parse_config_dict(
        config_dict,
    )

    since_ms = time.time() * 1000 - Config.parse_duration(config_args.since)
    exclude_users_with_email = config_args.exclude_emails
    exclude_users_with_appservice = config_args.exclude_app_service
    include_context = not config_args.only_users

    for database_config in config.database.databases:
        if "main" in database_config.databases:
            break

    engine = create_engine(database_config.config)

    with make_conn(database_config, engine, "review_recent_signups") as db_conn:
        # This generates a type of Cursor, not LoggingTransaction.
        user_infos = get_recent_users(db_conn.cursor(), since_ms, exclude_users_with_appservice)  # type: ignore[arg-type]

    for user_info in user_infos:
        if exclude_users_with_email and user_info.emails:
            continue

        if include_context:
            print_public_rooms = ""
            if user_info.public_rooms:
                print_public_rooms = "(" + ", ".join(user_info.public_rooms[:3])

                if len(user_info.public_rooms) > 3:
                    print_public_rooms += ", ..."

                print_public_rooms += ")"

            print("# Created:", datetime.fromtimestamp(user_info.creation_ts))
            print("# Email:", ", ".join(user_info.emails) or "None")
            print("# IPs:", ", ".join(user_info.ips))
            print(
                "# Number joined public rooms:",
                len(user_info.public_rooms),
                print_public_rooms,
            )
            print("# Number joined private rooms:", len(user_info.private_rooms))
            print("#")

        print(user_info.user_id)

        if include_context:
            print()


if __name__ == "__main__":
    main()
