import type { FastifyInstance } from 'fastify';
import type { FastifySchema } from 'fastify';
import type pg from 'pg';
import { z } from 'zod';
import type { Authenticate } from '../../../shared/auth.js';
import { requireRole } from '../../../shared/auth.js';
import { errorSchema } from '../../../shared/swagger.js';
import { parseJson } from '../../../shared/http-input.js';
import { AppError } from '../../../shared/errors.js';
import { PaymentsService } from '../application/payments-service.js';
import { InvalidWebhookSignatureError } from '../domain/errors.js';
import { PgPaymentRepository } from '../infrastructure/pg-payment-repository.js';
import { StripePaymentProvider } from '../infrastructure/stripe-payment-provider.js';
import {
  ALLOWED_TRANSITIONS,
  OrderNotFoundError,
  sendOrderStatusEmail,
  type OrderServicePort,
  type OrderState,
} from '../../orders/index.js';

export interface PaymentsRoutesDeps {
  pool: pg.Pool;
  authenticate: Authenticate;
  /** Inject webhook secret at wiring time (env-driven). */
  stripeWebhookSecret: string;
  stripeSecretKey?: string;
}

const orderIdParamSchema = z.object({ orderId: z.uuid() });

export async function registerPaymentsRoutes(
  app: FastifyInstance,
  deps: PaymentsRoutesDeps,
): Promise<void> {
  const provider = new StripePaymentProvider(deps.stripeWebhookSecret, deps.stripeSecretKey);
  const repository = new PgPaymentRepository(deps.pool);
  const ordersAdapter: OrderServicePort = {
    create: async () => {
      throw new Error('Payments should not call orders.create');
    },
    transition: async (id, state, _userId) => {
      const current = await deps.pool.query<{ state: OrderState }>(
        'SELECT state FROM orders_orders WHERE id = $1',
        [id],
      );
      const row = current.rows[0];
      if (!row) throw new OrderNotFoundError();
      if (!ALLOWED_TRANSITIONS[row.state]?.includes(state)) throw new OrderNotFoundError();
      await deps.pool.query(
        'UPDATE orders_orders SET state = $2, updated_at = now() WHERE id = $1',
        [id, state],
      );
      const refreshed = await deps.pool.query<{
        id: string;
        user_id: string;
        idempotency_key: string | null;
        state: OrderState;
        currency: 'EUR';
        subtotal_cents: number;
        discount_cents: number;
        tax_cents: number;
        total_cents: number;
        created_at: Date;
        updated_at: Date;
      }>(
        'SELECT id, user_id, idempotency_key, state, currency, subtotal_cents, discount_cents, tax_cents, total_cents, created_at, updated_at FROM orders_orders WHERE id = $1',
        [id],
      );
      const o = refreshed.rows[0];
      if (!o) throw new OrderNotFoundError();
      return {
        id: o.id,
        userId: o.user_id,
        idempotencyKey: o.idempotency_key,
        state: o.state,
        currency: o.currency,
        subtotalCents: o.subtotal_cents,
        discountCents: o.discount_cents,
        taxCents: o.tax_cents,
        totalCents: o.total_cents,
        createdAt: o.created_at,
        updatedAt: o.updated_at,
        items: [],
      };
    },
    getOrder: async () => undefined,
    listOrders: async () => [],
    getOrderAdmin: async () => undefined,
    transitionAdmin: async () => {
      throw new Error('Payments should not call transitionAdmin');
    },
  };
  const events = { emit: async () => undefined };
  const service = new PaymentsService({
    repository,
    orders: ordersAdapter,
    events,
    provider: 'stripe',
  });

  const webhookSchema: FastifySchema = {
    tags: ['Payments'],
    summary: 'Stripe webhook',
    description: 'Endpoint接收 Stripe webhook回调。无需认证。',
    response: { 400: errorSchema },
  };
  app.post('/payments/webhook', { schema: webhookSchema }, async (request, reply) => {
    const signature = request.headers['stripe-signature'];
    const signatureHeader = Array.isArray(signature) ? signature[0] : signature;
    const rawBody = (request.body as Buffer | string) ?? '';
    let event;
    try {
      event = provider.verifyWebhook(
        typeof rawBody === 'string' ? rawBody : rawBody.toString('utf8'),
        signatureHeader,
      );
    } catch (error) {
      if (error instanceof InvalidWebhookSignatureError) {
        throw new AppError(400, 'INVALID_PAYMENT_SIGNATURE', error.message);
      }
      throw error;
    }
    const outcome = await service.handleWebhook(event);
    // F-152: notify the customer when their payment succeeds (order confirmation).
    // Best-effort: a delivery failure is logged and never blocks the webhook.
    if (event.type === 'PaymentSucceeded' && event.orderId && outcome.kind === 'processed') {
      try {
        const emailRow = await deps.pool.query<{ email: string }>(
          `SELECT u.email FROM orders_orders o JOIN identity_users u ON u.id = o.user_id WHERE o.id = $1`,
          [event.orderId],
        );
        const to = emailRow.rows[0]?.email;
        if (to) {
          await sendOrderStatusEmail(deps.pool, {
            to,
            orderId: event.orderId,
            state: 'PAID',
          });
        }
      } catch (error) {
        request.log.warn({ err: error, orderId: event.orderId }, 'order_confirmation_email_failed');
      }
    }
    return reply.send({ result: outcome.kind });
  });

  // ── Admin management ───────────────────────────────────────────────────────
  const listPaymentsSchema: FastifySchema = {
    tags: ['Payments'],
    summary: 'List transactions (admin)',
    querystring: {
      type: 'object',
      properties: {
        limit: { type: 'integer', default: 50 },
        offset: { type: 'integer', default: 0 },
        status: {
          type: 'string',
          enum: ['requires_payment', 'succeeded', 'failed', 'refunded', 'chargeback'],
        },
        q: { type: 'string' },
      },
    },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.get('/admin/payments', { schema: listPaymentsSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const parsed = parseJson(
      z.object({
        limit: z.coerce.number().int().positive().max(100).optional().default(50),
        offset: z.coerce.number().int().min(0).optional().default(0),
        status: z
          .enum(['requires_payment', 'succeeded', 'failed', 'refunded', 'chargeback'])
          .optional(),
        q: z.string().optional(),
      }),
      request.query ?? {},
    );
    const conditions: string[] = [];
    const values: unknown[] = [];
    let i = 1;
    if (parsed.status) {
      conditions.push(`status = $${i++}`);
      values.push(parsed.status);
    }
    if (parsed.q) {
      conditions.push(`(provider_payment_id ILIKE $${i++})`);
      values.push(`%${parsed.q}%`);
    }
    const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
    const countResult = await deps.pool.query<{ count: string }>(
      `SELECT COUNT(*) FROM payments_transactions ${where}`,
      values,
    );
    const total = parseInt(countResult.rows[0]?.count ?? '0', 10);
    const rows = await deps.pool.query(
      `SELECT * FROM payments_transactions ${where} ORDER BY created_at DESC LIMIT $${i++} OFFSET $${i}`,
      [...values, parsed.limit, parsed.offset],
    );
    return reply.send({
      items: rows.rows.map((r) => ({
        id: r.id,
        provider: r.provider,
        providerPaymentId: r.provider_payment_id,
        orderId: r.order_id,
        amountCents: r.amount_cents,
        currency: r.currency,
        status: r.status,
        raw: r.raw,
        createdAt: r.created_at,
      })),
      total,
    });
  });

  const refundSchema: FastifySchema = {
    tags: ['Payments'],
    summary: 'Refund transaction (admin)',
    params: {
      type: 'object',
      required: ['id'],
      properties: { id: { type: 'string', format: 'uuid' } },
    },
    response: { 401: errorSchema, 403: errorSchema },
  };
  app.post('/admin/payments/:id/refund', { schema: refundSchema }, async (request, reply) => {
    const user = await deps.authenticate(request);
    requireRole(user, 'admin');
    const { id } = parseJson(z.object({ id: z.string().uuid() }), request.params);
    const tx = await deps.pool.query<{ status: string; provider_payment_id: string }>(
      `SELECT status, provider_payment_id FROM payments_transactions WHERE id = $1`,
      [id],
    );
    if (!tx.rows[0]) throw new AppError(404, 'NOT_FOUND', 'Transaction not found');
    if (tx.rows[0].status !== 'succeeded')
      throw new AppError(400, 'BAD_STATE', 'Can only refund succeeded payments');
    await provider.refund(tx.rows[0].provider_payment_id);
    await deps.pool.query(`UPDATE payments_transactions SET status = 'refunded' WHERE id = $1`, [
      id,
    ]);
    return reply.send({ ok: true });
  });

  const orderTxSchema: FastifySchema = {
    tags: ['Payments'],
    summary: 'Get order transactions',
    params: {
      type: 'object',
      required: ['orderId'],
      properties: { orderId: { type: 'string', format: 'uuid' } },
    },
    response: { 401: errorSchema },
  };
  app.get(
    '/payments/orders/:orderId/transactions',
    { schema: orderTxSchema },
    async (request, reply) => {
      await deps.authenticate(request);
      const { orderId } = orderIdParamSchema.parse(request.params);
      const rows = await repository.listByOrderId(orderId);
      return reply.send({ items: rows });
    },
  );
}
