import crypto from "node:crypto";
import { type Response, Router } from "express";
import { Prisma, SubscriptionStatus } from "@prisma/client";
import { env } from "../../config/env.js";
import { prisma } from "../../db/prisma.js";
import { hashPassword, signToken, verifyPassword } from "../../lib/auth.js";
import { HttpError } from "../../lib/errors.js";
import { isMailConfigured, sendMail } from "../../lib/mail.js";
import { requireAuth, type AuthenticatedRequest } from "../../middleware/auth.js";
import {
  forgotPasswordSchema,
  loginSchema,
  registerSchema,
  resetPasswordSchema
} from "./auth.schemas.js";

export const authRouter = Router();

type PasswordResetTokenRow = {
  id: string;
  userId: string;
  expiresAt: Date;
  usedAt: Date | null;
  userActive: boolean;
};

function logForgotPassword(event: string, payload: Record<string, unknown>) {
  console.info("[auth/forgot-password]", event, payload);
}

function isSecureCookie() {
  try {
    return new URL(env.APP_PUBLIC_URL).protocol === "https:";
  } catch {
    return true;
  }
}

function getAuthCookieDomain() {
  try {
    const { hostname } = new URL(env.APP_PUBLIC_URL);
    if (!hostname || hostname === "localhost" || /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) {
      return undefined;
    }

    return `.${hostname}`;
  } catch {
    return undefined;
  }
}

function setAuthCookie(res: Response, token: string) {
  res.cookie("auth_token", token, {
    httpOnly: true,
    secure: isSecureCookie(),
    sameSite: "lax",
    path: "/",
    domain: getAuthCookieDomain()
  });
}

function clearAuthCookie(res: Response) {
  res.clearCookie("auth_token", {
    httpOnly: true,
    secure: isSecureCookie(),
    sameSite: "lax",
    path: "/",
    domain: getAuthCookieDomain()
  });
}

function isSubscriptionActive(access: {
  status: SubscriptionStatus;
  startsAt: Date;
  expiresAt: Date | null;
}) {
  const now = Date.now();
  return (
    access.status === SubscriptionStatus.active &&
    access.startsAt.getTime() <= now &&
    (access.expiresAt === null || access.expiresAt.getTime() >= now)
  );
}

function hashResetToken(token: string) {
  return crypto.createHash("sha256").update(token).digest("hex");
}

function createPasswordResetUrl(token: string) {
  const url = new URL("/reset-password", env.APP_PUBLIC_URL);
  url.searchParams.set("token", token);
  return url.toString();
}

async function sendPasswordResetEmail(email: string, token: string) {
  const resetUrl = createPasswordResetUrl(token);

  await sendMail({
    to: email,
    subject: "Восстановление пароля",
    text: [
      "Вы запросили восстановление пароля.",
      `Перейдите по ссылке: ${resetUrl}`,
      `Ссылка действует ${env.PASSWORD_RESET_TTL_MINUTES} минут.`
    ].join("\n"),
    html: `
      <div style="font-family: Arial, sans-serif; line-height: 1.6; color: #10203b;">
        <h2 style="margin: 0 0 16px;">Восстановление пароля</h2>
        <p style="margin: 0 0 12px;">Вы запросили смену пароля для вашего аккаунта.</p>
        <p style="margin: 0 0 20px;">
          <a
            href="${resetUrl}"
            style="display: inline-block; padding: 12px 18px; border-radius: 12px; background: #21c58b; color: #ffffff; text-decoration: none; font-weight: 700;"
          >
            Сбросить пароль
          </a>
        </p>
        <p style="margin: 0 0 12px;">Или откройте ссылку вручную:</p>
        <p style="margin: 0 0 12px; word-break: break-all;">${resetUrl}</p>
        <p style="margin: 0; color: #5f7395;">Ссылка действует ${env.PASSWORD_RESET_TTL_MINUTES} минут.</p>
      </div>
    `
  });
}

authRouter.post("/register", async (req, res, next) => {
  try {
    const data = registerSchema.parse(req.body);
    const existingUser = await prisma.user.findUnique({ where: { email: data.email } });
    if (existingUser) throw new HttpError(409, "Пользователь уже существует");

    const user = await prisma.user.create({
      data: {
        email: data.email,
        passwordHash: await hashPassword(data.password),
        notificationSetting: {
          create: { signalsPushEnabled: true, resultsPushEnabled: false }
        }
      },
      select: { id: true, email: true, role: true }
    });

    const token = signToken({ userId: user.id, role: user.role });
    setAuthCookie(res, token);
    res.status(201).json({ token, user: { id: user.id, email: user.email, role: user.role } });
  } catch (error) {
    next(error);
  }
});

authRouter.post("/login", async (req, res, next) => {
  try {
    const data = loginSchema.parse(req.body);
    const user = await prisma.user.findUnique({ where: { email: data.email } });
    if (!user || !(await verifyPassword(data.password, user.passwordHash))) {
      throw new HttpError(401, "Неверный email или пароль");
    }

    const token = signToken({ userId: user.id, role: user.role });
    setAuthCookie(res, token);
    res.json({
      token,
      user: { id: user.id, email: user.email, role: user.role }
    });
  } catch (error) {
    next(error);
  }
});

authRouter.post("/logout", (_req, res) => {
  clearAuthCookie(res);
  res.status(204).send();
});

authRouter.post("/forgot-password", async (req, res, next) => {
  try {
    const data = forgotPasswordSchema.parse(req.body);
    logForgotPassword("requested", { email: data.email });

    if (!isMailConfigured()) {
      logForgotPassword("smtp_not_configured", { email: data.email });
      throw new HttpError(503, "Восстановление пароля по email пока не настроено");
    }

    const user = await prisma.user.findUnique({
      where: { email: data.email },
      select: { id: true, email: true, active: true }
    });

    if (!user) {
      logForgotPassword("user_not_found", { email: data.email });
    } else if (!user.active) {
      logForgotPassword("user_inactive", { email: user.email, userId: user.id });
    } else {
      const token = crypto.randomBytes(32).toString("hex");
      const tokenHash = hashResetToken(token);
      const expiresAt = new Date(Date.now() + env.PASSWORD_RESET_TTL_MINUTES * 60 * 1000);

      await prisma.$executeRaw`
        DELETE FROM "PasswordResetToken"
        WHERE "userId" = ${user.id} AND "usedAt" IS NULL
      `;

      await prisma.$executeRaw`
        INSERT INTO "PasswordResetToken" ("id", "userId", "tokenHash", "expiresAt")
        VALUES (${crypto.randomUUID()}, ${user.id}, ${tokenHash}, ${expiresAt})
      `;

      logForgotPassword("token_created", {
        email: user.email,
        userId: user.id,
        expiresAt: expiresAt.toISOString()
      });

      await sendPasswordResetEmail(user.email, token);
      logForgotPassword("email_sent", { email: user.email, userId: user.id });
    }

    res.json({
      message: "Если аккаунт с таким email существует, мы отправили письмо для восстановления пароля"
    });
  } catch (error) {
    next(error);
  }
});

authRouter.post("/reset-password", async (req, res, next) => {
  try {
    const data = resetPasswordSchema.parse(req.body);
    const tokenHash = hashResetToken(data.token);

    const [resetToken] = await prisma.$queryRaw<PasswordResetTokenRow[]>(Prisma.sql`
      SELECT
        prt."id",
        prt."userId",
        prt."expiresAt",
        prt."usedAt",
        u."active" AS "userActive"
      FROM "PasswordResetToken" prt
      INNER JOIN "User" u ON u."id" = prt."userId"
      WHERE prt."tokenHash" = ${tokenHash}
      LIMIT 1
    `);

    if (
      !resetToken ||
      resetToken.usedAt ||
      resetToken.expiresAt.getTime() < Date.now() ||
      !resetToken.userActive
    ) {
      throw new HttpError(400, "Ссылка для восстановления недействительна или уже истекла");
    }

    const passwordHash = await hashPassword(data.password);

    await prisma.$transaction(async (tx) => {
      await tx.user.update({
        where: { id: resetToken.userId },
        data: {
          passwordHash
        }
      });

      await tx.$executeRaw`
        UPDATE "PasswordResetToken"
        SET "usedAt" = ${new Date()}
        WHERE "id" = ${resetToken.id}
      `;

      await tx.$executeRaw`
        DELETE FROM "PasswordResetToken"
        WHERE "userId" = ${resetToken.userId}
          AND "usedAt" IS NULL
          AND "id" <> ${resetToken.id}
      `;
    });

    res.json({ message: "Пароль успешно обновлен" });
  } catch (error) {
    next(error);
  }
});

authRouter.get("/me", requireAuth, async (req: AuthenticatedRequest, res) => {
  await prisma.userBotAccess.updateMany({
    where: {
      userId: req.user!.id,
      status: SubscriptionStatus.active,
      expiresAt: {
        lt: new Date()
      }
    },
    data: {
      status: SubscriptionStatus.expired
    }
  });

  const user = await prisma.user.findUnique({
    where: { id: req.user!.id },
    select: {
      id: true,
      email: true,
      role: true,
      active: true,
      createdAt: true,
      notificationSetting: true,
      botAccesses: {
        include: {
          bot: {
            select: {
              id: true,
              key: true,
              name: true,
              sourceUrl: true,
              active: true
            }
          }
        },
        orderBy: [{ startsAt: "desc" }, { createdAt: "desc" }]
      }
    }
  });

  if (!user) {
    throw new HttpError(404, "Пользователь не найден");
  }

  const activeBotAccesses = user.botAccesses.filter(isSubscriptionActive);

  if (user.role === "admin" && user.botAccesses.length === 0) {
    const bots = await prisma.bot.findMany({
      where: { active: true },
      select: {
        id: true,
        key: true,
        name: true,
        sourceUrl: true,
        active: true
      },
      orderBy: { name: "asc" }
    });

    res.json({
      ...user,
      botAccesses: bots.map((bot) => ({
        id: `admin-${bot.id}`,
        grantedAt: user.createdAt,
        bot
      }))
    });
    return;
  }

  res.json({
    ...user,
    botAccesses: activeBotAccesses.map((access) => ({
      ...access,
      isActiveNow: true
    }))
  });
});
