import { TRPCError } from "@trpc/server";
import { eq, and, desc, count, sql, gte } from "drizzle-orm";
import { db } from "~/server/db";
import { spamFeedback, spamRules, auditLogs } from "~/server/db/schema";
import { classifyTextBERT, extractFeatures, ruleEngine } from "./spamshield/ml.engine";
import { checkReputation } from "./spamshield/reputation.api";
import type { ReputationResult } from "./spamshield/reputation.api";

function normalizePhoneNumber(phone: string): string {
  let cleaned = phone.replace(/[^\d+]/g, "");
  if (!cleaned.startsWith("+")) {
    if (cleaned.startsWith("1")) {
      cleaned = `+${cleaned}`;
    } else {
      cleaned = `+1${cleaned}`;
    }
  }
  return cleaned;
}

async function logAudit(
  userId: string | undefined,
  action: string,
  input: unknown,
  output: unknown,
  confidence?: number,
) {
  try {
    await db.insert(auditLogs).values({
      userId,
      action: `spamshield.${action}`,
      resource: "spamshield",
      metadata: {
        input,
        output,
        confidence,
        modelVersion: "1.0",
        timestamp: new Date().toISOString(),
      },
    });
  } catch (err) {
    console.error("[spamshield] Failed to write audit log:", err);
  }
}

export async function checkNumberReputation(phoneNumber: string) {
  const normalized = normalizePhoneNumber(phoneNumber);

  let result: {
    source: string;
    score: number;
    isSpam: boolean;
    confidence: number;
    category: string;
  } | null = null;

  if (normalized.length >= 10) {
    result = await checkReputation(normalized);
  }

  const response = {
    phoneNumber: normalized,
    isSpam: result?.isSpam ?? false,
    confidence: result?.confidence ?? 0,
    source: result?.source ?? "internal",
    score: result?.score ?? 0,
    category: result?.category ?? "unknown",
  };

  await logAudit(undefined, "checkNumber", { phoneNumber: normalized }, response, response.confidence);

  return response;
}

export async function classifySMS(text: string) {
  const classification = await classifyTextBERT(text);

  const response = {
    isSpam: classification.isSpam,
    confidence: classification.confidence,
    score: classification.score,
  };

  await logAudit(undefined, "classifySMS", { textLength: text.length }, response, response.confidence);

  return response;
}

export async function classifyCall(
  callerNumber: string,
  duration?: number,
  timeOfDay?: number,
) {
  const normalized = normalizePhoneNumber(callerNumber);
  const features = await extractFeatures({ callerNumber: normalized, duration, timeOfDay });

  const rules = await db
    .select()
    .from(spamRules)
    .where(and(eq(spamRules.isActive, true), eq(spamRules.isGlobal, true)))
    .orderBy(desc(spamRules.priority));

  const ruleMatch = await ruleEngine(
    rules.map((r) => ({
      id: r.id,
      ruleType: r.ruleType,
      pattern: r.pattern,
      action: r.action as "block" | "flag" | "allow" | "challenge",
      priority: r.priority,
    })),
    { phoneNumber: normalized },
  );

  const reputation = await checkReputation(normalized);

  const isSpam = ruleMatch?.action === "block" || (reputation?.isSpam ?? false);
  const confidence = Math.max(
    ruleMatch ? 0.9 : 0,
    reputation?.confidence ?? 0,
  );

  const response = {
    isSpam,
    confidence,
    callerNumber: normalized,
    matchedRule: ruleMatch
      ? { ruleId: ruleMatch.ruleId, action: ruleMatch.action, ruleType: ruleMatch.ruleType }
      : null,
    reputation: reputation
      ? { source: reputation.source, score: reputation.score, category: reputation.category }
      : null,
    features: {
      areaCode: features.areaCode,
      duration: features.duration,
      timeOfDay: features.timeOfDay,
    },
  };

  await logAudit(undefined, "classifyCall", { callerNumber: normalized, duration, timeOfDay }, response, confidence);

  return response;
}

export async function getRules(userId: string) {
  const userRules = await db
    .select()
    .from(spamRules)
    .where(and(eq(spamRules.userId, userId), eq(spamRules.isActive, true)))
    .orderBy(desc(spamRules.priority));

  const globalRules = await db
    .select()
    .from(spamRules)
    .where(and(eq(spamRules.isGlobal, true), eq(spamRules.isActive, true)))
    .orderBy(desc(spamRules.priority));

  return { userRules, globalRules };
}

export async function createRule(
  userId: string,
  ruleType: string,
  pattern: string,
  action: string,
  priority: number,
) {
  const [rule] = await db
    .insert(spamRules)
    .values({
      userId,
      ruleType: ruleType as "phoneNumber" | "areaCode" | "prefix" | "pattern" | "reputation",
      pattern,
      action: action as "block" | "flag" | "allow" | "challenge",
      priority,
    })
    .returning();

  await logAudit(userId, "createRule", { ruleType, pattern, action, priority }, rule);

  return rule;
}

export async function deleteRule(userId: string, ruleId: string) {
  const [existing] = await db
    .select()
    .from(spamRules)
    .where(and(eq(spamRules.id, ruleId), eq(spamRules.userId, userId)))
    .limit(1);

  if (!existing) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Rule not found" });
  }

  const [deleted] = await db
    .update(spamRules)
    .set({ isActive: false, updatedAt: new Date() })
    .where(eq(spamRules.id, ruleId))
    .returning();

  return deleted;
}

export async function submitFeedback(
  userId: string,
  phoneNumber: string,
  isSpam: boolean,
  feedbackType: string,
) {
  const normalized = normalizePhoneNumber(phoneNumber);
  const { createHash } = await import("node:crypto");
  const phoneNumberHash = createHash("sha256").update(normalized).digest("hex");

  const [feedback] = await db
    .insert(spamFeedback)
    .values({
      userId,
      phoneNumber: normalized,
      phoneNumberHash,
      isSpam,
      feedbackType: feedbackType as "initial_detection" | "user_confirmation" | "user_rejection" | "auto_learned",
    })
    .returning();

  await logAudit(userId, "submitFeedback", { phoneNumber: normalized, isSpam, feedbackType }, feedback);

  return feedback;
}

export async function getStats(userId: string, period: string = "month") {
  const now = new Date();
  let since: Date;

  switch (period) {
    case "day":
      since = new Date(now.getTime() - 24 * 60 * 60 * 1000);
      break;
    case "week":
      since = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
      break;
    case "year":
      since = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
      break;
    case "month":
    default:
      since = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());
      break;
  }

  const [totalResult] = await db
    .select({ count: count() })
    .from(spamFeedback)
    .where(and(eq(spamFeedback.userId, userId), gte(spamFeedback.createdAt, since)));

  const [spamResult] = await db
    .select({ count: count() })
    .from(spamFeedback)
    .where(
      and(
        eq(spamFeedback.userId, userId),
        eq(spamFeedback.isSpam, true),
        gte(spamFeedback.createdAt, since),
      ),
    );

  const [notSpamResult] = await db
    .select({ count: count() })
    .from(spamFeedback)
    .where(
      and(
        eq(spamFeedback.userId, userId),
        eq(spamFeedback.isSpam, false),
        gte(spamFeedback.createdAt, since),
      ),
    );

  const rulesCount = await db
    .select({ count: count() })
    .from(spamRules)
    .where(and(eq(spamRules.userId, userId), eq(spamRules.isActive, true)));

  const total = totalResult.count;
  const spam = spamResult.count;
  const notSpam = notSpamResult.count;
  const accuracy = total > 0 ? (spam / total) * 100 : 0;

  return {
    period,
    totalDetections: total,
    spamCount: spam,
    notSpamCount: notSpam,
    accuracy: Math.round(accuracy * 100) / 100,
    activeRules: rulesCount[0].count,
  };
}
