import { publicProcedure, protectedProcedure } from './router';
import { z } from 'zod';
import { eq, and, desc } from 'drizzle-orm';
import {
  kpiSnapshots,
  alertRules,
  alerts,
  scheduledReports,
  npsResponses,
  cohorts,
  cohortMembers,
} from '../../src/db/schema';
import {
  recordKPI,
  getLatestKPI,
  getKPIHistory,
  getAllLatestKPIs,
  checkKPIAgainstThreshold,
  getKPIStatus,
  KPI_THRESHOLDS,
  type KPIKey,
} from '../../src/lib/analytics/kpi-service';
import {
  evaluateAlertRules,
  formatAlertMessage,
  acknowledgeAlert,
  getUnsentAlerts,
  type SlackConfig,
} from '../../src/lib/analytics/slack-alerts';
import {
  generateWeeklyReport,
  generateMonthlyReport,
  formatReportMarkdown,
  formatReportSlackBlocks,
  createScheduledReport,
  getActiveScheduledReports,
  runDueReports,
} from '../../src/lib/analytics/report-generator';
import {
  createCohort,
  addCohortMember,
  getCohortAnalysis,
  listCohorts,
  getCohortSize,
  createMonthlyCohortTemplate,
  createWeeklyCohortTemplate,
  createFeatureCohortTemplate,
} from '../../src/lib/analytics/cohort-analysis';
import {
  submitNPSResponse,
  calculateNPS,
  getNPSResponses,
  getNPSOverTime,
  categorizeNPSScore,
  generateNPSSurveyEmail,
  generateNPSSurveyInAppPrompt,
  type NPSScore,
} from '../../src/lib/analytics/nps-service';

const KPIKeySchema = z.enum([
  'mau',
  'paying_users',
  'mrr',
  'conversion_rate',
  'churn_rate',
  'cac',
  'ltv',
  'nps',
  'viral_coefficient',
]);

const AlertConditionSchema = z.enum(['above', 'below', 'equals', 'increasing', 'decreasing']);
const AlertSeveritySchema = z.enum(['low', 'medium', 'high', 'critical']);
const ReportTypeSchema = z.enum(['weekly_kpi', 'monthly_kpi', 'cohort_analysis', 'nps_summary', 'custom']);
const ScheduleSchema = z.enum(['weekly', 'monthly', 'daily']);
const ReportFormatSchema = z.enum(['slack', 'email', 'both']);

export const analyticsRouter = {
  // --- KPI Endpoints ---

  getThresholds: publicProcedure.query(() => {
    return KPI_THRESHOLDS;
  }),

  getLatestKPI: publicProcedure
    .input(z.object({ kpiKey: KPIKeySchema }))
    .query(async ({ input, ctx }) => {
      return await getLatestKPI(ctx.db!, input.kpiKey as KPIKey);
    }),

  getAllLatestKPIs: protectedProcedure.query(async ({ ctx }) => {
    return await getAllLatestKPIs(ctx.db!);
  }),

  getKPIHistory: publicProcedure
    .input(z.object({
      kpiKey: KPIKeySchema,
      periodStart: z.string().datetime().optional(),
      periodEnd: z.string().datetime().optional(),
    }))
    .query(async ({ input, ctx }) => {
      return await getKPIHistory(
        ctx.db!,
        input.kpiKey as KPIKey,
        input.periodStart ? new Date(input.periodStart) : undefined,
        input.periodEnd ? new Date(input.periodEnd) : undefined,
      );
    }),

  recordKPI: protectedProcedure
    .input(z.object({
      kpiKey: KPIKeySchema,
      value: z.number(),
      periodStart: z.string().datetime(),
      periodEnd: z.string().datetime(),
      metadata: z.record(z.string(), z.unknown()).optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      const snapshot = await recordKPI(
        ctx.db!,
        input.kpiKey as KPIKey,
        input.value,
        new Date(input.periodStart),
        new Date(input.periodEnd),
        input.metadata,
      );

      const results = await evaluateAlertRules(ctx.db!, input.kpiKey as KPIKey, input.value);
      const triggeredAlerts = results.filter((r) => r.triggered);

      return {
        snapshot,
        triggeredAlerts,
      };
    }),

  checkThreshold: publicProcedure
    .input(z.object({
      kpiKey: KPIKeySchema,
      value: z.number(),
    }))
    .query(async ({ input }) => {
      const threshold = checkKPIAgainstThreshold(input.kpiKey as KPIKey, input.value);
      const status = getKPIStatus(input.kpiKey as KPIKey, input.value);
      return { ...threshold, status };
    }),

  // --- Alert Endpoints ---

  getAlertRules: protectedProcedure.query(async ({ ctx }) => {
    return await ctx.db!.select().from(alertRules).orderBy(desc(alertRules.createdAt));
  }),

  createAlertRule: protectedProcedure
    .input(z.object({
      name: z.string().min(1).max(200),
      kpiKey: KPIKeySchema,
      condition: AlertConditionSchema,
      threshold: z.number(),
      severity: AlertSeveritySchema,
      channelId: z.string().max(100).optional(),
      cooldownMinutes: z.number().int().min(1).max(1440).default(60),
    }))
    .mutation(async ({ input, ctx }) => {
      const result = await ctx.db!.insert(alertRules).values({
        name: input.name,
        kpiKey: input.kpiKey,
        condition: input.condition,
        threshold: input.threshold,
        severity: input.severity,
        channelId: input.channelId ?? null,
        isActive: true,
        cooldownMinutes: input.cooldownMinutes,
        createdBy: ctx.userId,
      }).returning();
      return result[0];
    }),

  updateAlertRule: protectedProcedure
    .input(z.object({
      id: z.number().int().positive(),
      name: z.string().min(1).max(200).optional(),
      condition: AlertConditionSchema.optional(),
      threshold: z.number().optional(),
      severity: AlertSeveritySchema.optional(),
      channelId: z.string().max(100).nullable().optional(),
      isActive: z.boolean().optional(),
      cooldownMinutes: z.number().int().min(1).max(1440).optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      const { id, ...updates } = input;
      const existing = await ctx.db!
        .select({ id: alertRules.id, createdBy: alertRules.createdBy })
        .from(alertRules)
        .where(eq(alertRules.id, id))
        .limit(1);
      const rule = existing[0];
      if (!rule) {
        throw new (await import('./router')).TRPCError({ code: 'NOT_FOUND', message: 'Alert rule not found' });
      }
      if (rule.createdBy !== ctx.userId) {
        throw new (await import('./router')).TRPCError({ code: 'FORBIDDEN', message: 'Not the rule owner' });
      }
      const result = await ctx.db!
        .update(alertRules)
        .set({ ...updates, updatedAt: new Date() })
        .where(eq(alertRules.id, id))
        .returning();
      return result[0];
    }),

  deleteAlertRule: protectedProcedure
    .input(z.object({ id: z.number().int().positive() }))
    .mutation(async ({ input, ctx }) => {
      const existing = await ctx.db!
        .select({ id: alertRules.id, createdBy: alertRules.createdBy })
        .from(alertRules)
        .where(eq(alertRules.id, input.id))
        .limit(1);
      const rule = existing[0];
      if (!rule) {
        throw new (await import('./router')).TRPCError({ code: 'NOT_FOUND', message: 'Alert rule not found' });
      }
      if (rule.createdBy !== ctx.userId) {
        throw new (await import('./router')).TRPCError({ code: 'FORBIDDEN', message: 'Not the rule owner' });
      }
      await ctx.db!.delete(alertRules).where(eq(alertRules.id, input.id));
      return { success: true };
    }),

  getAlerts: protectedProcedure
    .input(z.object({
      severity: AlertSeveritySchema.optional(),
      limit: z.number().int().min(1).max(200).default(50),
    }))
    .query(async ({ input, ctx }) => {
      const conditions: import('drizzle-orm').SQL[] = [];
      if (input.severity) {
        conditions.push(eq(alerts.severity, input.severity));
      }
      const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
      const query = ctx.db!.select().from(alerts).orderBy(desc(alerts.createdAt)).limit(input.limit);
      return whereClause ? await query.where(whereClause) : await query;
    }),

  acknowledgeAlert: protectedProcedure
    .input(z.object({
      alertId: z.number().int().positive(),
    }))
    .mutation(async ({ input, ctx }) => {
      const alert = await acknowledgeAlert(ctx.db!, input.alertId, ctx.userId!);
      return { success: !!alert, alert };
    }),

  getUnsentAlerts: publicProcedure.query(async ({ ctx }) => {
    return await getUnsentAlerts(ctx.db!);
  }),

  // --- Report Endpoints ---

  generateWeeklyReport: publicProcedure.query(async ({ ctx }) => {
    return await generateWeeklyReport(ctx.db!);
  }),

  generateMonthlyReport: publicProcedure.query(async ({ ctx }) => {
    return await generateMonthlyReport(ctx.db!);
  }),

  formatReportMarkdown: publicProcedure
    .input(z.object({
      periodStart: z.string().datetime(),
      periodEnd: z.string().datetime(),
      reportType: z.enum(['weekly', 'monthly']).default('weekly'),
    }))
    .query(async ({ input, ctx }) => {
      const report = input.reportType === 'weekly'
        ? await generateWeeklyReport(ctx.db!)
        : await generateMonthlyReport(ctx.db!);
      return await formatReportMarkdown(report);
    }),

  formatReportSlackBlocks: publicProcedure
    .input(z.object({
      reportType: z.enum(['weekly', 'monthly']).default('weekly'),
    }))
    .query(async ({ input, ctx }) => {
      const report = input.reportType === 'weekly'
        ? await generateWeeklyReport(ctx.db!)
        : await generateMonthlyReport(ctx.db!);
      return await formatReportSlackBlocks(report);
    }),

  getScheduledReports: publicProcedure.query(async ({ ctx }) => {
    return await getActiveScheduledReports(ctx.db!);
  }),

  createScheduledReport: protectedProcedure
    .input(z.object({
      name: z.string().min(1).max(200),
      reportType: ReportTypeSchema,
      schedule: ScheduleSchema,
      recipients: z.string().regex(/^[^\s@]+@[^\s@]+\.[^\s@]+(,[^\s@]+@[^\s@]+\.[^\s@]+)*$/, 'Each recipient must be a valid email'),
      format: ReportFormatSchema,
      metadata: z.record(z.string(), z.unknown()).optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      const report = await createScheduledReport(ctx.db!, {
        name: input.name,
        reportType: input.reportType,
        schedule: input.schedule,
        recipients: input.recipients,
        format: input.format,
        isActive: true,
        metadata: input.metadata ? JSON.stringify(input.metadata) : null,
        createdBy: ctx.userId,
      });
      return report;
    }),

  updateScheduledReport: protectedProcedure
    .input(z.object({
      id: z.number().int().positive(),
      name: z.string().min(1).max(200).optional(),
      reportType: ReportTypeSchema.optional(),
      schedule: ScheduleSchema.optional(),
      recipients: z.string().regex(/^[^\s@]+@[^\s@]+\.[^\s@]+(,[^\s@]+@[^\s@]+\.[^\s@]+)*$/, 'Each recipient must be a valid email').optional(),
      format: ReportFormatSchema.optional(),
      isActive: z.boolean().optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      const { id, ...updates } = input;
      const existing = await ctx.db!
        .select({ id: scheduledReports.id, createdBy: scheduledReports.createdBy })
        .from(scheduledReports)
        .where(eq(scheduledReports.id, id))
        .limit(1);
      const report = existing[0];
      if (!report) {
        throw new (await import('./router')).TRPCError({ code: 'NOT_FOUND', message: 'Scheduled report not found' });
      }
      if (report.createdBy !== ctx.userId) {
        throw new (await import('./router')).TRPCError({ code: 'FORBIDDEN', message: 'Not the report owner' });
      }
      const result = await ctx.db!
        .update(scheduledReports)
        .set({ ...updates, updatedAt: new Date() })
        .where(eq(scheduledReports.id, id))
        .returning();
      return result[0];
    }),

  // --- Cohort Endpoints ---

  getCohorts: publicProcedure
    .input(z.object({
      periodStart: z.string().datetime().optional(),
      periodEnd: z.string().datetime().optional(),
    }))
    .query(async ({ input, ctx }) => {
      return await listCohorts(
        ctx.db!,
        input.periodStart ? new Date(input.periodStart) : undefined,
        input.periodEnd ? new Date(input.periodEnd) : undefined,
      );
    }),

  createCohort: protectedProcedure
    .input(z.object({
      name: z.string().min(1).max(200),
      description: z.string().max(1000).optional(),
      periodStart: z.string().datetime(),
      periodEnd: z.string().datetime().optional(),
      filterCriteria: z.record(z.string(), z.unknown()),
    }))
    .mutation(async ({ input, ctx }) => {
      const cohort = await createCohort(ctx.db!, {
        name: input.name,
        description: input.description || '',
        periodStart: new Date(input.periodStart),
        periodEnd: input.periodEnd ? new Date(input.periodEnd) : undefined,
        filterCriteria: input.filterCriteria,
        createdBy: ctx.userId,
      });
      return cohort;
    }),

  addCohortMember: protectedProcedure
    .input(z.object({
      cohortId: z.number().int().positive(),
      userId: z.number().int().positive(),
    }))
    .mutation(async ({ input, ctx }) => {
      const existing = await ctx.db!
        .select({ id: cohorts.id, createdBy: cohorts.createdBy })
        .from(cohorts)
        .where(eq(cohorts.id, input.cohortId))
        .limit(1);
      const cohort = existing[0];
      if (!cohort) {
        throw new (await import('./router')).TRPCError({ code: 'NOT_FOUND', message: 'Cohort not found' });
      }
      if (cohort.createdBy !== ctx.userId) {
        throw new (await import('./router')).TRPCError({ code: 'FORBIDDEN', message: 'Not the cohort owner' });
      }
      await addCohortMember(ctx.db!, input.cohortId, input.userId);
      const size = await getCohortSize(ctx.db!, input.cohortId);
      return { success: true, cohortSize: size };
    }),

  getCohortAnalysis: publicProcedure
    .input(z.object({ cohortId: z.number().int().positive() }))
    .query(async ({ input, ctx }) => {
      return await getCohortAnalysis(ctx.db!, input.cohortId);
    }),

  getCohortTemplates: publicProcedure
    .input(z.object({
      type: z.enum(['monthly', 'weekly', 'feature']),
      featureName: z.string().max(100).optional(),
    }))
    .query(async ({ input }) => {
      switch (input.type) {
        case 'monthly':
          return createMonthlyCohortTemplate();
        case 'weekly':
          return createWeeklyCohortTemplate();
        case 'feature':
          return createFeatureCohortTemplate(input.featureName || 'unknown');
        default:
          return createMonthlyCohortTemplate();
      }
    }),

  // --- NPS Endpoints ---

  submitNPSResponse: protectedProcedure
    .input(z.object({
      score: z.number().int().min(0).max(10),
      userId: z.number().int().positive().optional(),
      feedback: z.string().max(2000).optional(),
      surveyId: z.string().max(100).optional(),
      respondentEmail: z.string().email().max(200).optional(),
    }))
    .mutation(async ({ input, ctx }) => {
      const response = await submitNPSResponse(ctx.db!, {
        score: input.score as NPSScore,
        userId: input.userId,
        feedback: input.feedback,
        surveyId: input.surveyId,
        respondentEmail: input.respondentEmail,
      });
      return response;
    }),

  calculateNPS: publicProcedure
    .input(z.object({
      periodStart: z.string().datetime().optional(),
      periodEnd: z.string().datetime().optional(),
    }))
    .query(async ({ input, ctx }) => {
      return await calculateNPS(
        ctx.db!,
        input.periodStart ? new Date(input.periodStart) : undefined,
        input.periodEnd ? new Date(input.periodEnd) : undefined,
      );
    }),

  getNPSResponses: protectedProcedure
    .input(z.object({
      category: z.enum(['detractor', 'passive', 'promoter']).optional(),
      periodStart: z.string().datetime().optional(),
      periodEnd: z.string().datetime().optional(),
      limit: z.number().int().min(1).max(200).default(50),
    }))
    .query(async ({ input, ctx }) => {
      return await getNPSResponses(
        ctx.db!,
        input.category,
        input.periodStart ? new Date(input.periodStart) : undefined,
        input.periodEnd ? new Date(input.periodEnd) : undefined,
        input.limit,
      );
    }),

  getNPSOverTime: publicProcedure
    .input(z.object({
      granularity: z.enum(['weekly', 'monthly']).default('weekly'),
    }))
    .query(async ({ input, ctx }) => {
      return await getNPSOverTime(ctx.db!, input.granularity);
    }),

  getNPSSurveyPrompt: publicProcedure.query(() => {
    return generateNPSSurveyInAppPrompt();
  }),
};
