/** مسیرهای گزارش مردمی، پرونده‌ها، فضای کاری سازمان و کسب‌وکار */
import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { and, desc, eq, inArray, sql } from 'drizzle-orm';
import { getDb } from '../../database/client.js';
import {
  reports, cases, tasks, organizations, departments, organizationMembers, users, organizationMessages,
  communicationChannels, businesses, shops, businessOfferings, officialResponses, notifications, analyticsEvents,
} from '../../database/schema/index.js';
import {
  submitReport, moderateReport, assignCase, changeCaseStatus, reopenCase, addOfficialResponse,
  createTaskFromCase, organizationScorecard, assertCaseAccess,
} from '../../modules/reports/service.js';
import { previewRouting } from '../../modules/reports/routing.js';
import { enqueueOutbound, adapterStatus } from '../../modules/communications/service.js';
import { requireAuth } from '../../guards/index.js';
import { can, scopedOrganizationIds, scopedBusinessIds } from '../../modules/rbac/service.js';
import { recordAudit, AuditActions } from '../../modules/audit/service.js';
import { Errors } from '../../common/errors.js';
import { indexEntity } from '../../modules/search/indexer.js';

const submitSchema = z.object({
  kind: z.enum(['report', 'complaint', 'suggestion', 'request', 'question', 'alert']),
  categoryKey: z.string().min(2),
  title: z.string().min(5),
  body: z.string().min(20),
  severity: z.enum(['critical', 'high', 'normal', 'low']).optional(),
  regionId: z.string().uuid().optional(),
  lat: z.number().optional(),
  lng: z.number().optional(),
  address: z.string().optional(),
  privacy: z.enum(['public', 'public_after_review', 'private_to_org', 'anonymous_public', 'anonymous_to_org']).optional(),
  targetOrganizationId: z.string().uuid().optional(),
  targetBusinessId: z.string().uuid().optional(),
  evidence: z.array(z.object({
    kind: z.enum(['image', 'video', 'document', 'link', 'location']),
    url: z.string().optional(), mediaId: z.string().uuid().optional(), caption: z.string().optional(),
  })).max(12).optional(),
});

export default async function civicRoutes(app: FastifyInstance) {
  /* ------------------------------ ثبت گزارش -------------------------------- */
  app.post('/reports', { config: { rateLimit: { max: 12, timeWindow: '1 hour' } } }, async (req) =>
    submitReport(req.principal, submitSchema.parse(req.body), { ip: req.ip }));

  /** پیگیری گزارش‌های خود کاربر */
  app.get('/reports/mine', async (req) => {
    const principal = requireAuth(req);
    const db = getDb();
    const rows = await db.select({ r: reports, c: cases })
      .from(reports).leftJoin(cases, eq(cases.reportId, reports.id))
      .where(eq(reports.reporterUserId, principal.userId)).orderBy(desc(reports.createdAt));
    return { items: rows.map(({ r, c }) => ({ report: r, case: c })) };
  });

  /* ------------------------- صف بررسی (Moderation) -------------------------- */
  app.get('/moderation/reports', async (req) => {
    const principal = requireAuth(req);
    if (!can(principal, 'report.moderate')) throw Errors.forbidden();
    const rows = await getDb().select().from(reports)
      .where(inArray(reports.status, ['submitted', 'moderation'])).orderBy(desc(reports.createdAt)).limit(100);
    return { items: rows };
  });

  app.post('/moderation/reports/:id', async (req) => {
    const principal = requireAuth(req);
    const { id } = req.params as { id: string };
    const body = z.object({
      action: z.enum(['accept', 'reject', 'duplicate']),
      note: z.string().optional(),
      duplicateOfId: z.string().uuid().optional(),
    }).parse(req.body);
    return moderateReport(principal, id, body);
  });

  /** پیش‌نمایش تصمیم موتور مسیریابی بدون اعمال آن */
  app.post('/moderation/routing/preview', async (req) => {
    const principal = requireAuth(req);
    if (!can(principal, 'report.route')) throw Errors.forbidden();
    const body = z.object({
      categoryKey: z.string(), kind: z.string(), severity: z.string(),
      regionId: z.string().uuid().nullable().optional(), title: z.string(), body: z.string(),
    }).parse(req.body);
    return previewRouting({ ...body, regionId: body.regionId ?? null });
  });

  /* ------------------------- فضای کاری سازمان ------------------------------- */
  app.get('/org/:organizationId/dashboard', async (req) => {
    const principal = requireAuth(req);
    const { organizationId } = req.params as { organizationId: string };
    assertOrgAccess(principal, organizationId);
    const db = getDb();
    const [org] = await db.select().from(organizations).where(eq(organizations.id, organizationId));
    if (!org) throw Errors.notFound('سازمان یافت نشد.');
    const [openCases, recentTasks, staff, depts, channels, inbox] = await Promise.all([
      db.select().from(cases).where(and(eq(cases.organizationId, organizationId), sql`${cases.status} NOT IN ('resolved','closed')`)).orderBy(desc(cases.createdAt)).limit(50),
      db.select().from(tasks).where(eq(tasks.organizationId, organizationId)).orderBy(desc(tasks.createdAt)).limit(30),
      db.select({ member: organizationMembers, user: { id: users.id, name: users.displayName, email: users.email } })
        .from(organizationMembers).innerJoin(users, eq(users.id, organizationMembers.userId))
        .where(eq(organizationMembers.organizationId, organizationId)),
      db.select().from(departments).where(eq(departments.organizationId, organizationId)),
      db.select().from(communicationChannels).where(eq(communicationChannels.organizationId, organizationId)),
      db.select().from(organizationMessages).where(eq(organizationMessages.toOrganizationId, organizationId)).orderBy(desc(organizationMessages.createdAt)).limit(20),
    ]);
    const scorecard = await organizationScorecard(organizationId);
    const overdue = openCases.filter((c) => c.slaBreachedAt !== null);
    return { organization: org, openCases, overdue, tasks: recentTasks, staff, departments: depts, channels, inbox, scorecard, adapters: adapterStatus() };
  });

  app.get('/org/:organizationId/cases', async (req) => {
    const principal = requireAuth(req);
    const { organizationId } = req.params as { organizationId: string };
    assertOrgAccess(principal, organizationId);
    const q = req.query as Record<string, string | undefined>;
    const conditions = [eq(cases.organizationId, organizationId)];
    if (q.status) conditions.push(eq(cases.status, q.status));
    const rows = await getDb().select({ c: cases, r: reports }).from(cases)
      .innerJoin(reports, eq(reports.id, cases.reportId)).where(and(...conditions)).orderBy(desc(cases.createdAt)).limit(200);
    return {
      items: rows.map(({ c, r }) => ({
        ...c,
        // در حالت ناشناس، هویت گزارش‌دهنده حتی برای سازمان هم پنهان می‌ماند
        reporter: r.privacy.startsWith('anonymous') ? null : r.reporterUserId,
        category: r.categoryKey, severity: r.severity, body: r.body, evidenceLocation: { lat: r.lat, lng: r.lng },
      })),
    };
  });

  app.post('/cases/:id/assign', async (req) => {
    const principal = requireAuth(req);
    const { id } = req.params as { id: string };
    const body = z.object({ assigneeUserId: z.string().uuid().optional(), departmentId: z.string().uuid().optional(), note: z.string().optional() }).parse(req.body);
    return assignCase(principal, id, body);
  });

  app.post('/cases/:id/status', async (req) => {
    const principal = requireAuth(req);
    const { id } = req.params as { id: string };
    const body = z.object({ to: z.enum(['routed', 'assigned', 'under_investigation', 'response_requested', 'response_received', 'resolved', 'closed']), note: z.string().optional() }).parse(req.body);
    return changeCaseStatus(principal, id, body.to, body.note);
  });

  app.post('/cases/:id/reopen', async (req) => {
    const principal = requireAuth(req);
    const { id } = req.params as { id: string };
    const { reason } = z.object({ reason: z.string().min(5) }).parse(req.body);
    return reopenCase(principal, id, reason);
  });

  app.post('/cases/:id/responses', async (req) => {
    const principal = requireAuth(req);
    const { id } = req.params as { id: string };
    const body = z.object({
      body: z.string().min(10), attachments: z.array(z.string()).optional(),
      sourceRef: z.string().optional(), responderTitle: z.string().optional(),
    }).parse(req.body);
    return addOfficialResponse(principal, id, body);
  });

  app.post('/cases/:id/tasks', async (req) => {
    const principal = requireAuth(req);
    const { id } = req.params as { id: string };
    const body = z.object({
      title: z.string().min(3), description: z.string().optional(),
      assigneeUserId: z.string().uuid().optional(), deadline: z.string().optional(), priority: z.string().optional(),
    }).parse(req.body);
    return createTaskFromCase(principal, id, { ...body, deadline: body.deadline ? new Date(body.deadline) : undefined });
  });

  app.get('/cases/:id', async (req) => {
    const { id } = req.params as { id: string };
    const row = await assertCaseAccess(req.principal, id, 'read');
    return row;
  });

  /* ------------------------- وظایف داخلی سازمان ----------------------------- */
  app.patch('/tasks/:id', async (req) => {
    const principal = requireAuth(req);
    const { id } = req.params as { id: string };
    const db = getDb();
    const [task] = await db.select().from(tasks).where(eq(tasks.id, id));
    if (!task) throw Errors.notFound('وظیفه یافت نشد.');
    if (!can(principal, 'task.manage', { organizationId: task.organizationId })) throw Errors.forbidden();
    const body = z.object({
      status: z.enum(['open', 'in_progress', 'blocked', 'done', 'cancelled']).optional(),
      assigneeUserId: z.string().uuid().optional(), deadline: z.string().optional(), priority: z.string().optional(),
    }).parse(req.body);
    const [updated] = await db.update(tasks).set({
      status: body.status ?? task.status,
      assigneeUserId: body.assigneeUserId ?? task.assigneeUserId,
      deadline: body.deadline ? new Date(body.deadline) : task.deadline,
      priority: body.priority ?? task.priority,
      updatedAt: new Date(),
    }).where(eq(tasks.id, id)).returning();
    return updated;
  });

  /* ---------------------- پیام رسمی بین سازمان‌ها ---------------------------- */
  app.post('/org/:organizationId/messages', async (req) => {
    const principal = requireAuth(req);
    const { organizationId } = req.params as { organizationId: string };
    assertOrgAccess(principal, organizationId);
    if (!can(principal, 'organization.message', { organizationId })) throw Errors.forbidden();
    const body = z.object({
      toOrganizationId: z.string().uuid(), subject: z.string().min(3), body: z.string().min(10),
      priority: z.enum(['critical', 'high', 'normal', 'low']).optional(),
      deadline: z.string().optional(), actionRequested: z.boolean().optional(),
      relatedCaseId: z.string().uuid().optional(), attachments: z.array(z.string()).optional(),
    }).parse(req.body);
    const [row] = await getDb().insert(organizationMessages).values({
      fromOrganizationId: organizationId, toOrganizationId: body.toOrganizationId,
      subject: body.subject, body: body.body, priority: body.priority ?? 'normal',
      deadline: body.deadline ? new Date(body.deadline) : null,
      actionRequested: body.actionRequested ?? false, relatedCaseId: body.relatedCaseId ?? null,
      attachments: body.attachments ?? [], createdBy: principal.userId,
    }).returning();
    await recordAudit({ action: 'organization.message_sent', actorId: principal.userId, entityType: 'organization_message', entityId: row.id });
    return row;
  });

  /* ------------------------- کانال‌های ارتباطی ------------------------------ */
  app.post('/org/:organizationId/channels', async (req) => {
    const principal = requireAuth(req);
    const { organizationId } = req.params as { organizationId: string };
    assertOrgAccess(principal, organizationId);
    if (!can(principal, 'communication.manage', { organizationId })) throw Errors.forbidden();
    const body = z.object({
      provider: z.enum(['telegram', 'eitaa', 'email', 'phone', 'web']),
      handle: z.string().min(2), ownerName: z.string().optional(), priority: z.number().optional(),
      departmentId: z.string().uuid().optional(),
    }).parse(req.body);
    const [row] = await getDb().insert(communicationChannels).values({
      organizationId, provider: body.provider, handle: body.handle,
      ownerName: body.ownerName ?? null, priority: body.priority ?? 100, departmentId: body.departmentId ?? null,
    }).returning();
    await recordAudit({ action: AuditActions.COMMUNICATION_CHANGE, actorId: principal.userId, entityType: 'communication_channel', entityId: row.id, after: { provider: body.provider, handle: body.handle } });
    return row;
  });

  /** ارسال پیام از طریق کانال؛ وضعیت واقعی تحویل بازگردانده می‌شود (بدون جعل) */
  app.post('/channels/:channelId/send', async (req) => {
    const principal = requireAuth(req);
    const { channelId } = req.params as { channelId: string };
    const db = getDb();
    const [channel] = await db.select().from(communicationChannels).where(eq(communicationChannels.id, channelId));
    if (!channel) throw Errors.notFound('کانال یافت نشد.');
    if (!can(principal, 'communication.manage', { organizationId: channel.organizationId, businessId: channel.businessId })) throw Errors.forbidden();
    const body = z.object({ text: z.string().min(1), caseId: z.string().uuid().optional() }).parse(req.body);
    const result = await enqueueOutbound(channelId, body, principal.userId);
    return {
      ...result,
      // شفافیت کامل درباره وضعیت: اگر سرویس پیکربندی نشده، صریحاً اعلام می‌شود
      humanState: result.state === 'delivered' ? 'ارسال شد'
        : result.state === 'unavailable' ? 'سرویس پیام‌رسان پیکربندی نشده است؛ فقط پیوند مستقیم ساخته شد.'
        : result.state === 'failed' ? 'ارسال ناموفق بود.' : 'در صف ارسال',
    };
  });

  /* --------------------------- فضای کاری کسب‌وکار --------------------------- */
  app.get('/business/:businessId/dashboard', async (req) => {
    const principal = requireAuth(req);
    const { businessId } = req.params as { businessId: string };
    if (!scopedBusinessIds(principal).includes(businessId) && !can(principal, 'business.verify')) throw Errors.forbidden();
    const db = getDb();
    const [biz] = await db.select().from(businesses).where(eq(businesses.id, businessId));
    if (!biz) throw Errors.notFound('کسب‌وکار یافت نشد.');
    const [branches, offerings, bizCases, responses] = await Promise.all([
      db.select().from(shops).where(eq(shops.businessId, businessId)),
      db.select().from(businessOfferings).where(eq(businessOfferings.businessId, businessId)),
      db.select({ c: cases, r: reports }).from(cases).innerJoin(reports, eq(reports.id, cases.reportId)).where(eq(cases.businessId, businessId)),
      db.select().from(officialResponses).where(eq(officialResponses.businessId, businessId)),
    ]);
    return {
      business: biz, branches, offerings,
      cases: bizCases.map(({ c, r }) => ({ ...c, category: r.categoryKey, body: r.body, reporter: r.privacy.startsWith('anonymous') ? null : r.reporterUserId })),
      responses,
    };
  });

  app.patch('/business/:businessId', async (req) => {
    const principal = requireAuth(req);
    const { businessId } = req.params as { businessId: string };
    if (!can(principal, 'business.manage', { businessId })) throw Errors.forbidden();
    const body = z.object({
      description: z.string().optional(), phone: z.string().optional(), website: z.string().optional(),
      socials: z.record(z.string(), z.string()).optional(), ownerNamePublic: z.string().optional(),
    }).parse(req.body);
    const [row] = await getDb().update(businesses).set({ ...body, updatedAt: new Date() }).where(eq(businesses.id, businessId)).returning();
    await indexEntity('business', businessId);
    return row;
  });
}

/** بررسی عضویت کاربر در سازمان — پایه‌ی جداسازی چند-مستاجری */
function assertOrgAccess(principal: { grants: unknown } & { userId: string }, organizationId: string) {
  const p = principal as Parameters<typeof scopedOrganizationIds>[0];
  if (scopedOrganizationIds(p).includes(organizationId)) return;
  if (can(p, 'organization.manage')) return; // مدیران سراسری
  throw Errors.forbidden('شما به فضای کاری این سازمان دسترسی ندارید.');
}
