import { Router } from 'express';
import Joi from 'joi';
import pick from 'lodash/pick';

import { Op } from 'sequelize';
import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
import logger from '../libs/logger';
import { authenticate } from '../libs/security';
import { formatMetadata } from '../libs/util';
import { Meter, PaymentCurrency, PaymentMethod } from '../store/models';

const router = Router();
const auth = authenticate<Meter>({ component: true, roles: ['owner', 'admin'] });

const meterSchema = Joi.object({
  name: Joi.string().max(64).required(),
  event_name: Joi.string().max(64).required(),
  aggregation_method: Joi.string().valid('sum', 'count', 'last').default('sum'),
  unit: Joi.string().max(32).required(),
  currency_id: Joi.string().max(40).optional(),
  decimal: Joi.number().integer().min(2).max(18).default(10),
  description: Joi.string().max(255).allow('').optional(),
  metadata: MetadataSchema,
  component_did: Joi.string().max(40).optional(),
  token: Joi.object({
    tokenFactoryAddress: Joi.string().required(),
  }).optional(),
}).unknown(true);

const updateMeterSchema = Joi.object({
  name: Joi.string().max(64).optional(),
  description: Joi.string().max(255).allow('').optional(),
  status: Joi.string().valid('active', 'inactive').optional(),
});

const listSchema = createListParamSchema<{ event_name?: string }>({
  event_name: Joi.string().empty(''),
});

router.get('/', auth, async (req, res) => {
  try {
    const { page, pageSize, ...query } = await listSchema.validateAsync(req.query, { stripUnknown: true });
    const where = getWhereFromKvQuery(query.q);

    if (typeof query.livemode === 'boolean') {
      where.livemode = query.livemode;
    }
    if (query.event_name) {
      where.event_name = query.event_name;
    }

    const { rows: list, count } = await Meter.findAndCountAll({
      where,
      order: getOrder(query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
      offset: (page - 1) * pageSize,
      limit: pageSize,
      include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
    });

    res.json({ count, list, paging: { page, pageSize } });
  } catch (err) {
    logger.error('Error listing meters', err);
    res.status(400).json({ error: err?.message });
  }
});

router.post('/', auth, async (req, res) => {
  try {
    const { error } = meterSchema.validate(req.body);
    if (error) {
      return res.status(400).json({ error: `Meter create request invalid: ${error.message}` });
    }

    const existing = await Meter.findOne({
      where: { event_name: req.body.event_name },
    });
    if (existing) {
      return res.status(409).json({ error: `Meter with event_name "${req.body.event_name}" already exists` });
    }

    if (['count', 'last'].includes(req.body.aggregation_method)) {
      return res.status(400).json({ error: 'Aggregation method is not supported' });
    }

    const needArcblockMethod = req.body.token?.tokenFactoryAddress || !req.body.currency_id;
    const arcblockMethod = needArcblockMethod
      ? await PaymentMethod.findOne({ where: { livemode: !!req.livemode, type: 'arcblock' } })
      : null;
    if (needArcblockMethod && !arcblockMethod) {
      throw new Error('ArcBlock payment method not found');
    }

    let tokenConfig: Record<string, any> | undefined;
    if (req.body.token?.tokenFactoryAddress) {
      const client = arcblockMethod!.getOcapClient();
      const { state: tokenFactoryState } = await client.getTokenFactoryState({
        address: req.body.token.tokenFactoryAddress,
      });
      if (!tokenFactoryState) {
        return res.status(400).json({ error: 'Token factory not found on chain' });
      }
      tokenConfig = {
        address: tokenFactoryState.token.address,
        symbol: tokenFactoryState.token.symbol,
        name: tokenFactoryState.token.name,
        decimal: tokenFactoryState.token.decimal,
        token_factory_address: tokenFactoryState.address,
      };
    }

    const meterData = {
      ...pick(req.body, ['name', 'event_name', 'aggregation_method', 'unit', 'currency_id', 'description', 'metadata']),
      livemode: !!req.livemode,
      created_via: req.user?.via || 'api',
      status: req.body.status || 'active',
      metadata: formatMetadata(req.body.metadata),
    };

    if (!meterData.currency_id) {
      const paymentCurrency = await PaymentCurrency.createForMeter(meterData, arcblockMethod!.id, tokenConfig, {
        decimal: req.body.decimal,
      });
      meterData.currency_id = paymentCurrency.id;
    }

    const meter = await Meter.create(meterData);

    const result = await Meter.findByPk(meter.id, {
      include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
    });

    logger.info('Meter created', { meterId: meter.id, eventName: meter.event_name });
    return res.json(result);
  } catch (err) {
    logger.error('create meter failed', { error: err?.message, request: req.body });
    return res.status(400).json({ error: err?.message });
  }
});

// Public endpoint: only returns safe fields, no auth required
const PUBLIC_METER_FIELDS = ['id', 'name', 'event_name', 'status', 'unit', 'description', 'currency_id'] as const;

router.get('/public/:id', async (req, res) => {
  try {
    const meter = await Meter.findOne({
      where: {
        [Op.or]: [{ id: req.params.id }, { event_name: req.params.id }],
      },
      include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
    });

    if (!meter) {
      return res.status(404).json({ error: 'Meter not found' });
    }

    return res.json({
      ...pick(meter.toJSON(), PUBLIC_METER_FIELDS),
      paymentCurrency: (meter as any).paymentCurrency
        ? pick((meter as any).paymentCurrency.toJSON(), ['id', 'name', 'symbol', 'decimal', 'logo', 'type'])
        : null,
    });
  } catch (err) {
    logger.error('get public meter failed', { error: err?.message, meterId: req.params.id });
    return res.status(400).json({ error: err?.message });
  }
});

router.get('/:id', auth, async (req, res) => {
  try {
    const meter = await Meter.findOne({
      where: {
        [Op.or]: [{ id: req.params.id }, { event_name: req.params.id }],
      },
      include: [{ model: PaymentCurrency.scope('withRechargeConfig'), as: 'paymentCurrency' }],
    });

    if (!meter) {
      return res.status(404).json({ error: 'Meter not found' });
    }

    return res.json(meter);
  } catch (err) {
    logger.error('get meter failed', { error: err?.message, meterId: req.params.id });
    return res.status(400).json({ error: err?.message });
  }
});

router.put('/:id', auth, async (req, res) => {
  try {
    const { error } = updateMeterSchema.validate(pick(req.body, ['name', 'description', 'status']));
    if (error) {
      return res.status(400).json({ error: `Meter update request invalid: ${error.message}` });
    }

    const meter = await Meter.findByPk(req.params.id, {
      include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
    });
    if (!meter) {
      return res.status(404).json({ error: 'Meter not found' });
    }

    const updateData: any = {
      ...pick(req.body, ['name', 'description', 'status']),
      unit: req.body.unit || meter.unit,
      updated_by: req.user?.did,
    };

    if (req.body.metadata) {
      const { error: metadataError } = MetadataSchema.validate(req.body.metadata);
      if (metadataError) {
        return res.status(400).json({ error: `metadata invalid: ${metadataError.message}` });
      }
      updateData.metadata = formatMetadata(req.body.metadata);
    }

    await meter.update(updateData);

    return res.json(meter);
  } catch (err) {
    logger.error('update meter failed', { error: err?.message, meterId: req.params.id });
    return res.status(400).json({ error: err?.message });
  }
});

router.put('/:id/activate', auth, async (req, res) => {
  try {
    const meter = await Meter.findByPk(req.params.id, {
      include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
    });
    if (!meter) {
      return res.status(404).json({ error: 'Meter not found' });
    }

    if (meter.status === 'active') {
      return res.status(400).json({ error: 'Meter is already active' });
    }

    await meter.update({
      status: 'active',
      updated_by: req.user?.did,
    });
    return res.json(meter);
  } catch (err) {
    logger.error('activate meter failed', { error: err?.message, meterId: req.params.id });
    return res.status(400).json({ error: err?.message });
  }
});

router.put('/:id/deactivate', auth, async (req, res) => {
  try {
    const meter = await Meter.findByPk(req.params.id, {
      include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
    });
    if (!meter) {
      return res.status(404).json({ error: 'Meter not found' });
    }

    if (meter.status === 'inactive') {
      return res.status(400).json({ error: 'Meter is already inactive' });
    }

    await meter.update({
      status: 'inactive',
      updated_by: req.user?.did,
    });

    return res.json(meter);
  } catch (err) {
    logger.error('deactivate meter failed', { error: err?.message, meterId: req.params.id });
    return res.status(400).json({ error: err?.message });
  }
});

export default router;
