import csv
import decimal
import logging
import os
import time
from datetime import datetime, timezone, date
from email.utils import formatdate
from typing import List

from sqlalchemy.inspection import inspect as sa_inspect
from pyramid_mailer.message import Message

import rhodecode
from rhodecode.lib.celerylib.tasks import transform_legacy_email_config, get_mailer
from rhodecode.lib.statsd_client import StatsdClient
from rhodecode.lib.type_utils import str2bool
from rhodecode.model.db import User
from rhodecode.model.meta import Base
from rhodecode.model.user import UserModel

log = logging.getLogger(__name__)
SENSITIVE_FIELDS = {"password", "api_key", "user_auth_tokens", "user_ssh_keys"}
EMAIL_SUBJECT = "Action needed: inactive account scheduled for deletion"
EMAIL_BODY_TEMPLATE = """Hi {first_name},
We haven’t seen any activity on your account for for at least {days_inactive} days.
To keep your account, please sign in and use the service within the next {reactivate_days} days.
If no action is taken, your account will be permanently deleted.
Need help? https://rhodecode.com/support"""


INACTIVITY_DAYS = 183
DAYS_FOR_REACTIVATION = 30


def find_inactive_users(inactive_days: int = INACTIVITY_DAYS):
    log.info(f"Find users who are inactive for {inactive_days} days")
    inactive_users = User.list_inactive(older_then_days=inactive_days)
    log.debug(
        f"Found {len(inactive_users)} inactive users. Inactive user names: {[u.username for u in inactive_users]}"
    )
    has_emails = [u for u in inactive_users if u.email]
    no_emails = [u for u in inactive_users if not u.email]
    return has_emails, no_emails


def dump_users_to_csv(
    users: List[Base],
    *,
    model=None,
    directory: str = ".",
    filename_prefix: str = "inactive_users",
    sensitive_fields: set[str] = SENSITIVE_FIELDS,
) -> str | None:
    """
    Dump ORM user rows into a CSV.
    - Auto-discovers mapped columns (no relations) via SQLAlchemy.
    - Blanks sensitive fields.
    - filename_prefix: to filename_prefix will be added UTC timestamp + .csv extension
    Returns the absolute file path.
    """
    # 1) Determine the model and column list (skip relationships by using mapper.columns)
    if not users:
        log.error("No users found, nothing to dump")
        return None

    if not all(isinstance(u, Base) for u in users):
        log.error("Provide SQLAlchemy model list")
        return None

    if model is None:
        model = type(users[0])

    mapper = sa_inspect(model)
    column_keys = [col.key for col in mapper.columns]

    # 2) Build filename with UTC timestamp
    ts = datetime.now(timezone.utc)
    stamp = f"{ts.strftime('%d-%m-%Y_%H:%M:%S')}"
    filename = f"{filename_prefix}_{stamp}.csv"

    os.makedirs(directory, exist_ok=True)
    filepath = os.path.abspath(os.path.join(directory, filename))

    # 3) Simple serializer for CSV-safe values
    def _ser(v):
        if v is None:
            return ""
        if isinstance(v, (datetime, date)):
            return v.isoformat(sep=" ", timespec="seconds")
        if isinstance(v, decimal.Decimal):
            return format(v, "f")
        return str(v)

    sensitive = set(sensitive_fields)

    try:
        with open(filepath, "w", newline="", encoding="utf-8") as f:
            log.info(f"Writing user data for {len(users)} user(s) to {filepath}")
            writer = csv.DictWriter(f, fieldnames=column_keys, extrasaction="ignore")
            writer.writeheader()

            for u in users:
                row = {}
                for key in column_keys:
                    row[key] = "" if key in sensitive else _ser(getattr(u, key, None))
                writer.writerow(row)
    except Exception as e:
        log.error(f"Failed to write users data for {filepath}: {e}")
        return None

    return filepath


def batch_send_email_to_inactive_users(
    users: list[User],
    subject: str = EMAIL_SUBJECT,
    body_template: str = EMAIL_BODY_TEMPLATE,
    inactive_days: int = INACTIVITY_DAYS,
    days_for_reactivation: int = DAYS_FOR_REACTIVATION,
) -> tuple[list[str], list[str]]:
    emails_sent = []
    emails_failed = []

    if not users:
        log.warning("No recipients provided")
        return emails_sent, emails_failed

    email_config = rhodecode.CONFIG

    mail_server = email_config.get("smtp_server") or None
    dev_mod = str2bool(email_config.get("development_email"))

    if mail_server is None and not dev_mod:
        log.error(
            "SMTP server information missing. Sending email failed. "
            "Make sure that `smtp_server` variable is configured "
            "inside the .ini file"
        )
        return emails_sent, emails_failed

    conf_prefix = email_config.get("email_prefix", None)
    prefix = f"{conf_prefix} " if conf_prefix else ""
    subject = f"{prefix}{subject}"
    transformed_email_confing = transform_legacy_email_config(email_config, mail_server)
    extra_headers = {"Date": formatdate(time.time())}

    mailer = get_mailer(transformed_email_confing, email_config)

    users_with_emails = list(filter(lambda u: u.email, users))

    log.debug(f"Total emails to send: {len(users_with_emails)}")

    for u in users_with_emails:
        try:
            log.debug(f"Sending email to {u.email}")
            body = body_template.format(
                first_name=u.first_name, days_inactive=inactive_days, reactivate_days=days_for_reactivation
            )

            message = Message(
                subject=subject,
                sender=transformed_email_confing["default_sender"],
                recipients=[u.email],
                body=body,
                extra_headers=extra_headers,
            )
            mailer.send_immediately(message)

            statsd = StatsdClient.statsd
            if statsd:
                statsd.incr("rhodecode_email_sent_total")

            emails_sent.append(u)

            log.debug(f"Sent email to {u.email}")
        except Exception as e:
            log.error(f"Mail sending failed for recipient {u.email}", e)
            emails_failed.append(u)
            time.sleep(1)

    return emails_sent, emails_failed


def filter_reactivated_users(csv_path: str, inactive_days: int = INACTIVITY_DAYS) -> tuple[list[User], list[User]]:
    """
    takes users from the csv file, and compare them with existing users if user have been reactivated
    """
    inactive_users = []
    reactivated_users = []
    if not os.path.exists(csv_path) or not os.path.isfile(csv_path) or not os.access(csv_path, os.R_OK):
        log.error(f"File {csv_path} does not exist or is not readable")
        return inactive_users, reactivated_users

    ids = []
    with open(csv_path, "r", encoding="utf-8") as f:
        reader = csv.DictReader(f, delimiter=",")
        for row in reader:
            if not row["user_id"]:
                log.error("Can't find user_id in csv file, skipping line")
                continue
            ids.append(int(row["user_id"]))

    csv_users = UserModel().list(ids)
    has_emails, no_emails = find_inactive_users(inactive_days)
    current_inactive_users = [*has_emails, *no_emails]
    current_inactive_users_ids = {u.user_id for u in current_inactive_users}

    for u in csv_users:
        if u.user_id in current_inactive_users_ids:
            inactive_users.append(u)
        else:
            reactivated_users.append(u)

    return inactive_users, reactivated_users


def delete_inactive_users_and_their_assets(users: List[User]) -> tuple[list[dict], list[dict]]:
    deleted_users = []
    failed_to_delete = []
    for u in users:
        log.debug(f"Deleting user {u.username}")
        user_info = {
            "id": u.user_id,
            "username": u.username,
            "email": u.email,
        }
        try:
            UserModel().delete(u.user_id)
            deleted_users.append(user_info)
        except Exception as e:
            log.error(f"Failed to delete user {u.username}: {e}")
            failed_to_delete.append(user_info)

    return deleted_users, failed_to_delete
