import { randomUUID } from 'node:crypto';
import { Router } from 'express';
import type { Database } from '../db/types.js';
import { ConflictError, NotFoundError } from '../domain/errors.js';
import { normalizeIdentifier, normalizePersian } from '../domain/normalization.js';
import type { AuthService } from '../services/auth-service.js';
import { appendAudit } from '../services/audit.js';
import { PERMISSIONS } from '../services/rbac.js';
import type { UserService } from '../services/user-service.js';
import { formatJalaliDateTime } from '../services/jalali.js';
import { auditContext, csrfProtection, requireAuth, requirePermission, setFlash } from './middleware.js';

export function adminRoutes(db: Database, users: UserService, auth: AuthService): Router {
  const router = Router();
  router.use(requireAuth);

  router.get('/products', requirePermission(PERMISSIONS.PRODUCTS_MANAGE), async (_req, res, next) => {
    try {
      const rows = await db.query<any>('SELECT * FROM products ORDER BY archived_at NULLS FIRST, name LIMIT 500');
      res.render('admin/products', { title: 'کالاها', rows: rows.rows });
    } catch (error) { next(error); }
  });
  router.post('/products', requirePermission(PERMISSIONS.PRODUCTS_MANAGE), csrfProtection, async (req, res, next) => {
    try {
      const code = String(req.body.code ?? '').trim(); const name = String(req.body.name ?? '').trim(); const unit = String(req.body.unit ?? '').trim();
      if (!code || !name || !unit) throw new ConflictError('کد، نام و واحد کالا الزامی است.');
      const id = randomUUID();
      await db.transaction(async (tx) => {
        await tx.query(
          `INSERT INTO products(id, code, normalized_code, name, normalized_name, unit, created_by)
           VALUES ($1,$2,$3,$4,$5,$6,$7)`,
          [id, code, normalizeIdentifier(code), name, normalizePersian(name), unit, req.currentUser!.id]
        );
        await appendAudit(tx, auditContext(req), { action: 'product.created', entityType: 'product', entityId: id, newValues: { code, name, unit } });
      });
      setFlash(req, 'success', 'کالا با موفقیت ثبت شد.'); res.redirect('/products');
    } catch (error) { next(error); }
  });
  router.post('/products/:id/archive', requirePermission(PERMISSIONS.PRODUCTS_MANAGE), csrfProtection, async (req, res, next) => {
    try {
      await db.transaction(async (tx) => {
        const changed = await tx.query(`UPDATE products SET active = false, archived_at = now(), updated_at = now() WHERE id = $1`, [req.params.id]);
        if (!changed.rowCount) throw new NotFoundError('کالا یافت نشد.');
        await appendAudit(tx, auditContext(req), { action: 'product.archived', entityType: 'product', entityId: String(req.params.id) });
      });
      setFlash(req, 'success', 'کالا بایگانی شد و سوابق تاریخی حفظ شدند.'); res.redirect('/products');
    } catch (error) { next(error); }
  });

  router.get('/requesters', requirePermission(PERMISSIONS.REQUESTERS_MANAGE), async (_req, res, next) => {
    try {
      const rows = await db.query<any>('SELECT * FROM requesters ORDER BY archived_at NULLS FIRST, name LIMIT 500');
      res.render('admin/requesters', { title: 'درخواست‌کنندگان', rows: rows.rows });
    } catch (error) { next(error); }
  });
  router.post('/requesters', requirePermission(PERMISSIONS.REQUESTERS_MANAGE), csrfProtection, async (req, res, next) => {
    try {
      const name = String(req.body.name ?? '').trim();
      if (name.length < 2) throw new ConflictError('نام درخواست‌کننده الزامی است.');
      const id = randomUUID();
      await db.transaction(async (tx) => {
        await tx.query(`INSERT INTO requesters(id, code, name, normalized_name, created_by) VALUES ($1,$2,$3,$4,$5)`,
          [id, String(req.body.code ?? '').trim() || null, name, normalizePersian(name), req.currentUser!.id]);
        await appendAudit(tx, auditContext(req), { action: 'requester.created', entityType: 'requester', entityId: id, newValues: { name } });
      });
      if (req.accepts(['json', 'html']) === 'json') return res.status(201).json({ id, name });
      setFlash(req, 'success', 'درخواست‌کننده با موفقیت ثبت شد.'); res.redirect('/requesters');
    } catch (error) { next(error); }
  });
  router.post('/requesters/:id/archive', requirePermission(PERMISSIONS.REQUESTERS_MANAGE), csrfProtection, async (req, res, next) => {
    try {
      await db.transaction(async (tx) => {
        const changed = await tx.query(`UPDATE requesters SET active = false, archived_at = now(), updated_at = now() WHERE id = $1`, [req.params.id]);
        if (!changed.rowCount) throw new NotFoundError('درخواست‌کننده یافت نشد.');
        await appendAudit(tx, auditContext(req), { action: 'requester.archived', entityType: 'requester', entityId: String(req.params.id) });
      });
      setFlash(req, 'success', 'درخواست‌کننده غیرفعال شد و سوابق حفظ شدند.'); res.redirect('/requesters');
    } catch (error) { next(error); }
  });

  router.get('/users', requirePermission(PERMISSIONS.USERS_MANAGE), async (req, res, next) => {
    try {
      const [userRows, roles, branches, warehouses] = await Promise.all([
        db.query<any>(
          `SELECT u.*, COALESCE(string_agg(r.name_fa, '، '), '') AS role_names,
           COALESCE(string_agg(r.code, ','), '') AS role_codes
           FROM users u LEFT JOIN user_roles ur ON ur.user_id = u.id LEFT JOIN roles r ON r.id = ur.role_id
           GROUP BY u.id ORDER BY u.created_at`
        ),
        db.query<any>('SELECT * FROM roles ORDER BY name_fa'),
        db.query<any>('SELECT id, name FROM branches WHERE active = true ORDER BY name'),
        db.query<any>('SELECT id, name FROM warehouses WHERE active = true ORDER BY name')
      ]);
      const temporary = req.session.temporaryPassword; delete req.session.temporaryPassword;
      res.render('admin/users', { title: 'کاربران', rows: userRows.rows, roles: roles.rows, branches: branches.rows,
        warehouses: warehouses.rows, temporary, formatJalaliDateTime });
    } catch (error) { next(error); }
  });
  router.post('/users', requirePermission(PERMISSIONS.USERS_MANAGE), csrfProtection, async (req, res, next) => {
    try {
      const created = await users.create(req.currentUser!.id, {
        name: String(req.body.name ?? ''), username: String(req.body.username ?? ''), email: String(req.body.email ?? ''),
        roleCodes: Array.isArray(req.body.roleCodes) ? req.body.roleCodes.map(String) : [String(req.body.roleCodes ?? '')].filter(Boolean),
        branchId: String(req.body.branchId ?? '') || null, warehouseId: String(req.body.warehouseId ?? '') || null
      }, auditContext(req));
      req.session.temporaryPassword = { username: String(req.body.username), password: created.temporaryPassword };
      setFlash(req, 'success', 'کاربر ایجاد شد؛ رمز موقت فقط در همین نمایش بعدی قابل مشاهده است.'); res.redirect('/users');
    } catch (error) { next(error); }
  });
  router.post('/users/:id/reset-password', requirePermission(PERMISSIONS.USERS_MANAGE), csrfProtection, async (req, res, next) => {
    try {
      const userId = String(req.params.id);
      const temporaryPassword = await auth.resetPassword(req.currentUser!.id, userId, auditContext(req));
      const target = await db.query<{ username: string }>('SELECT username FROM users WHERE id = $1', [userId]);
      req.session.temporaryPassword = { username: target.rows[0]?.username ?? 'کاربر', password: temporaryPassword };
      setFlash(req, 'success', 'رمز موقت تولید شد و تمام نشست‌های قبلی کاربر باطل شدند.'); res.redirect('/users');
    } catch (error) { next(error); }
  });
  router.post('/users/:id/active', requirePermission(PERMISSIONS.USERS_MANAGE), csrfProtection, async (req, res, next) => {
    try {
      const active = req.body.active === 'true';
      await users.setActive(req.currentUser!.id, String(req.params.id), active, auditContext(req));
      setFlash(req, 'success', active ? 'کاربر فعال شد.' : 'کاربر غیرفعال و نشست‌های او باطل شد.'); res.redirect('/users');
    } catch (error) { next(error); }
  });

  router.get('/roles', requirePermission(PERMISSIONS.ROLES_MANAGE), async (_req, res, next) => {
    try {
      const [roles, permissions, assigned] = await Promise.all([
        db.query<any>('SELECT * FROM roles ORDER BY name_fa'),
        db.query<any>('SELECT * FROM permissions ORDER BY module, name_fa'),
        db.query<any>('SELECT role_id, permission_id FROM role_permissions')
      ]);
      res.render('admin/roles', { title: 'نقش‌ها و ماتریس مجوز', roles: roles.rows, permissions: permissions.rows, assigned: assigned.rows });
    } catch (error) { next(error); }
  });
  router.post('/roles/:id/permissions', requirePermission(PERMISSIONS.ROLES_MANAGE), csrfProtection, async (req, res, next) => {
    try {
      const selected = new Set(Array.isArray(req.body.permissions) ? req.body.permissions.map(String) : [String(req.body.permissions ?? '')].filter(Boolean));
      await db.transaction(async (tx) => {
        const role = await tx.query<{ code: string }>('SELECT code FROM roles WHERE id = $1 FOR UPDATE', [req.params.id]);
        if (!role.rowCount) throw new NotFoundError('نقش یافت نشد.');
        if (role.rows[0]!.code === 'super_admin' && !selected.has(PERMISSIONS.ROLES_MANAGE)) {
          throw new ConflictError('مجوز مدیریت نقش‌ها از نقش مدیر کل قابل حذف نیست.');
        }
        const before = await tx.query<{ code: string }>(
          `SELECT p.code FROM permissions p JOIN role_permissions rp ON rp.permission_id = p.id WHERE rp.role_id = $1`, [req.params.id]
        );
        await tx.query('DELETE FROM role_permissions WHERE role_id = $1', [req.params.id]);
        for (const code of selected) await tx.query(
          `INSERT INTO role_permissions(role_id, permission_id) SELECT $1, id FROM permissions WHERE code = $2`, [req.params.id, code]
        );
        await appendAudit(tx, auditContext(req), { action: 'role.permissions_changed', entityType: 'role', entityId: String(req.params.id),
          oldValues: { permissions: before.rows.map((p) => p.code) }, newValues: { permissions: [...selected] } });
      });
      setFlash(req, 'success', 'ماتریس مجوز با موفقیت ذخیره شد.'); res.redirect('/roles');
    } catch (error) { next(error); }
  });

  router.get('/audit', requirePermission(PERMISSIONS.AUDIT_VIEW_OPERATIONAL), async (req, res, next) => {
    try {
      const rows = await db.query<any>(
        `SELECT a.*, u.name AS actor FROM audit_events a LEFT JOIN users u ON u.id = a.actor_id
         ORDER BY a.occurred_at DESC LIMIT 500`
      );
      res.render('admin/audit', { title: 'تاریخچه ممیزی', rows: rows.rows, canViewSecurity: req.currentUser!.permissions.has(PERMISSIONS.SECURITY_AUDIT_VIEW), formatJalaliDateTime });
    } catch (error) { next(error); }
  });

  router.get('/settings', requirePermission(PERMISSIONS.SETTINGS_MANAGE), async (_req, res, next) => {
    try {
      const rows = await db.query<any>('SELECT * FROM system_settings ORDER BY key');
      res.render('admin/settings', { title: 'تنظیمات سامانه', rows: rows.rows });
    } catch (error) { next(error); }
  });
  router.post('/settings', requirePermission(PERMISSIONS.SETTINGS_MANAGE), csrfProtection, async (req, res, next) => {
    try {
      const allowed = ['notifications.enabled', 'company.support_phone', 'reports.retention_days'];
      const key = String(req.body.key ?? ''); if (!allowed.includes(key)) throw new ConflictError('کلید تنظیمات مجاز نیست.');
      const value = String(req.body.value ?? '');
      await db.transaction(async (tx) => {
        const before = await tx.query<any>('SELECT value FROM system_settings WHERE key = $1', [key]);
        await tx.query(
          `INSERT INTO system_settings(key, value, updated_by) VALUES ($1,$2::jsonb,$3)
           ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value, updated_by = EXCLUDED.updated_by, updated_at = now()`,
          [key, JSON.stringify(value), req.currentUser!.id]
        );
        await appendAudit(tx, auditContext(req), { action: 'settings.changed', entityType: 'setting', entityId: key,
          oldValues: before.rows[0] ?? null, newValues: { value } });
      });
      setFlash(req, 'success', 'تنظیمات ذخیره شد.'); res.redirect('/settings');
    } catch (error) { next(error); }
  });
  return router;
}
