import { and, asc, count, desc, eq, inArray, isNull, sql } from 'drizzle-orm';
import {
  permissionTemplatePermissions,
  permissionTemplates,
  userPermissionTemplateAssignments
} from '@/db/schema';
import type { Permission } from '@/lib/auth/permissions';
import { normalizePermissions } from '@/lib/auth/permissions';
import { db } from '@/lib/db';
import type {
  PermissionCatalogResponse,
  PermissionTemplate,
  PermissionTemplateFilters,
  PermissionTemplateMutationPayload
} from '../api/types';
import { getPermissionCatalogGroups } from '@/features/permission-management/server/permission-management-catalog';
import {
  logPermissionAuditEvent,
  PERMISSION_AUDIT_ACTION,
  toPermissionAuditValue
} from './permission-audit-service';

const sortMap = {
  created_at: permissionTemplates.createdAt,
  name: permissionTemplates.name,
  code: permissionTemplates.code,
  updated_at: permissionTemplates.updatedAt
} as const;

function normalizeText(value?: string | null) {
  const normalized = value?.trim();
  return normalized ? normalized : null;
}

function serializePermissionTemplate(
  row: {
    id: number;
    organizationId: string;
    code: string;
    name: string;
    description: string | null;
    isSystem: boolean;
    isDefault: boolean;
    isActive: boolean;
    deletedAt: Date | null;
    createdAt: Date;
    updatedAt: Date;
  },
  permissions: Permission[],
  assignedUserCount: number
): PermissionTemplate {
  return {
    id: row.id,
    organization_id: row.organizationId,
    code: row.code,
    name: row.name,
    description: row.description,
    is_system: row.isSystem,
    is_default: row.isDefault,
    is_active: row.isActive,
    deleted_at: row.deletedAt ? row.deletedAt.toISOString() : null,
    assigned_user_count: assignedUserCount,
    permissions,
    created_at: row.createdAt.toISOString(),
    updated_at: row.updatedAt.toISOString()
  };
}

export function getPermissionCatalog(): PermissionCatalogResponse {
  return {
    success: true,
    groups: getPermissionCatalogGroups()
  };
}

export async function listPermissionTemplatesForOrganization({
  organizationId,
  filters
}: {
  organizationId: string;
  filters: PermissionTemplateFilters;
}) {
  const page = Number(filters.page ?? 1);
  const limit = Number(filters.limit ?? 10);
  const search = normalizeText(filters.search);
  const status = normalizeText(filters.status);

  const clauses = [
    eq(permissionTemplates.organizationId, organizationId),
    isNull(permissionTemplates.deletedAt),
    ...(search
      ? [
          sql`(${permissionTemplates.name} ilike ${`%${search}%`} or ${permissionTemplates.code} ilike ${`%${search}%`})`
        ]
      : []),
    ...(status === 'active'
      ? [eq(permissionTemplates.isActive, true)]
      : status === 'inactive'
        ? [eq(permissionTemplates.isActive, false)]
        : [])
  ];

  const where = clauses.length > 1 ? and(...clauses) : clauses[0];
  let orderByClause = desc(permissionTemplates.createdAt);

  if (filters.sort) {
    try {
      const sortItems = JSON.parse(filters.sort) as Array<{
        id: keyof typeof sortMap;
        desc: boolean;
      }>;
      const primarySort = sortItems[0];

      if (primarySort?.id && sortMap[primarySort.id]) {
        orderByClause = primarySort.desc
          ? desc(sortMap[primarySort.id])
          : asc(sortMap[primarySort.id]);
      }
    } catch {
      orderByClause = desc(permissionTemplates.createdAt);
    }
  }

  const baseQuery = db
    .select({
      id: permissionTemplates.id,
      organizationId: permissionTemplates.organizationId,
      code: permissionTemplates.code,
      name: permissionTemplates.name,
      description: permissionTemplates.description,
      isSystem: permissionTemplates.isSystem,
      isDefault: permissionTemplates.isDefault,
      isActive: permissionTemplates.isActive,
      deletedAt: permissionTemplates.deletedAt,
      createdAt: permissionTemplates.createdAt,
      updatedAt: permissionTemplates.updatedAt
    })
    .from(permissionTemplates)
    .where(where);

  const countSubquery = baseQuery.as('scoped_permission_templates');
  const [totalResult] = await db.select({ value: count() }).from(countSubquery);
  const offset = (page - 1) * limit;
  const rows = await baseQuery.orderBy(orderByClause).limit(limit).offset(offset);

  const templateIds = rows.map((row) => row.id);
  const templatePermissionRows = templateIds.length
    ? await db
        .select({
          templateId: permissionTemplatePermissions.templateId,
          permission: permissionTemplatePermissions.permission
        })
        .from(permissionTemplatePermissions)
        .where(inArray(permissionTemplatePermissions.templateId, templateIds))
    : [];

  const assignmentCountRows = templateIds.length
    ? await db
        .select({
          templateId: userPermissionTemplateAssignments.templateId,
          value: count()
        })
        .from(userPermissionTemplateAssignments)
        .where(
          and(
            inArray(userPermissionTemplateAssignments.templateId, templateIds),
            eq(userPermissionTemplateAssignments.isActive, true)
          )
        )
        .groupBy(userPermissionTemplateAssignments.templateId)
    : [];

  const permissionsByTemplate = new Map<number, Permission[]>();
  for (const row of templatePermissionRows) {
    const current = permissionsByTemplate.get(row.templateId) ?? [];
    permissionsByTemplate.set(row.templateId, normalizePermissions([...current, row.permission]));
  }

  const assignmentCountByTemplate = new Map<number, number>();
  for (const row of assignmentCountRows) {
    assignmentCountByTemplate.set(row.templateId, row.value);
  }

  return {
    offset,
    limit,
    total: totalResult?.value ?? 0,
    permissionTemplates: rows.map((row) =>
      serializePermissionTemplate(
        row,
        permissionsByTemplate.get(row.id) ?? [],
        assignmentCountByTemplate.get(row.id) ?? 0
      )
    )
  };
}

export async function getPermissionTemplateById({
  organizationId,
  templateId
}: {
  organizationId: string;
  templateId: number;
}) {
  const [row] = await db
    .select({
      id: permissionTemplates.id,
      organizationId: permissionTemplates.organizationId,
      code: permissionTemplates.code,
      name: permissionTemplates.name,
      description: permissionTemplates.description,
      isSystem: permissionTemplates.isSystem,
      isDefault: permissionTemplates.isDefault,
      isActive: permissionTemplates.isActive,
      deletedAt: permissionTemplates.deletedAt,
      createdAt: permissionTemplates.createdAt,
      updatedAt: permissionTemplates.updatedAt
    })
    .from(permissionTemplates)
    .where(
      and(
        eq(permissionTemplates.id, templateId),
        eq(permissionTemplates.organizationId, organizationId),
        isNull(permissionTemplates.deletedAt)
      )
    )
    .limit(1);

  if (!row) {
    throw new Error('NOT_FOUND');
  }

  const permissionRows = await db
    .select({
      permission: permissionTemplatePermissions.permission
    })
    .from(permissionTemplatePermissions)
    .where(eq(permissionTemplatePermissions.templateId, row.id));

  const [assignmentCountResult] = await db
    .select({
      value: count()
    })
    .from(userPermissionTemplateAssignments)
    .where(
      and(
        eq(userPermissionTemplateAssignments.templateId, row.id),
        eq(userPermissionTemplateAssignments.isActive, true)
      )
    );

  return serializePermissionTemplate(
    row,
    normalizePermissions(permissionRows.map((item) => item.permission)),
    assignmentCountResult?.value ?? 0
  );
}

export async function createPermissionTemplateForOrganization({
  organizationId,
  actorUserId,
  payload
}: {
  organizationId: string;
  actorUserId: string;
  payload: PermissionTemplateMutationPayload;
}) {
  const now = new Date();
  const permissions = normalizePermissions(payload.permissions);

  const createdTemplate = await db.transaction(async (tx) => {
    const [template] = await tx
      .insert(permissionTemplates)
      .values({
        organizationId,
        code: payload.code.trim(),
        name: payload.name.trim(),
        description: payload.description?.trim() || null,
        isDefault: payload.is_default,
        isActive: payload.is_active,
        createdBy: actorUserId,
        updatedBy: actorUserId,
        createdAt: now,
        updatedAt: now
      })
      .returning();

    if (permissions.length > 0) {
      await tx.insert(permissionTemplatePermissions).values(
        permissions.map((permission) => ({
          templateId: template.id,
          permission,
          createdAt: now
        }))
      );
    }

    return template;
  });

  await logPermissionAuditEvent({
    organizationId,
    actorUserId,
    templateId: createdTemplate.id,
    action: PERMISSION_AUDIT_ACTION.TEMPLATE_CREATE,
    reason: payload.reason ?? null,
    after: toPermissionAuditValue({
      code: payload.code,
      name: payload.name,
      permissions
    })
  });

  return createdTemplate.id;
}

export async function updatePermissionTemplateForOrganization({
  organizationId,
  actorUserId,
  templateId,
  payload
}: {
  organizationId: string;
  actorUserId: string;
  templateId: number;
  payload: PermissionTemplateMutationPayload;
}) {
  const current = await getPermissionTemplateById({ organizationId, templateId });
  const now = new Date();
  const permissions = normalizePermissions(payload.permissions);

  await db.transaction(async (tx) => {
    await tx
      .update(permissionTemplates)
      .set({
        code: payload.code.trim(),
        name: payload.name.trim(),
        description: payload.description?.trim() || null,
        isDefault: payload.is_default,
        isActive: payload.is_active,
        updatedBy: actorUserId,
        updatedAt: now
      })
      .where(eq(permissionTemplates.id, templateId));

    await tx
      .delete(permissionTemplatePermissions)
      .where(eq(permissionTemplatePermissions.templateId, templateId));

    if (permissions.length > 0) {
      await tx.insert(permissionTemplatePermissions).values(
        permissions.map((permission) => ({
          templateId,
          permission,
          createdAt: now
        }))
      );
    }
  });

  await logPermissionAuditEvent({
    organizationId,
    actorUserId,
    templateId,
    action: PERMISSION_AUDIT_ACTION.TEMPLATE_UPDATE,
    reason: payload.reason ?? null,
    before: toPermissionAuditValue(current),
    after: toPermissionAuditValue({
      ...current,
      code: payload.code,
      name: payload.name,
      description: payload.description ?? null,
      is_default: payload.is_default,
      is_active: payload.is_active,
      permissions
    })
  });
}

export async function clonePermissionTemplateForOrganization({
  organizationId,
  actorUserId,
  templateId,
  reason
}: {
  organizationId: string;
  actorUserId: string;
  templateId: number;
  reason?: string | null;
}) {
  const template = await getPermissionTemplateById({ organizationId, templateId });
  const now = new Date();

  const [createdTemplate] = await db
    .insert(permissionTemplates)
    .values({
      organizationId,
      code: `${template.code}-copy-${template.id}`,
      name: `${template.name} (Copy)`,
      description: template.description,
      isDefault: false,
      isActive: true,
      createdBy: actorUserId,
      updatedBy: actorUserId,
      createdAt: now,
      updatedAt: now
    })
    .returning();

  if (template.permissions.length > 0) {
    await db.insert(permissionTemplatePermissions).values(
      template.permissions.map((permission) => ({
        templateId: createdTemplate.id,
        permission,
        createdAt: now
      }))
    );
  }

  await logPermissionAuditEvent({
    organizationId,
    actorUserId,
    templateId: createdTemplate.id,
    action: PERMISSION_AUDIT_ACTION.TEMPLATE_CLONE,
    reason: reason ?? null,
    before: toPermissionAuditValue(template),
    after: toPermissionAuditValue({
      ...template,
      id: createdTemplate.id,
      code: createdTemplate.code,
      name: createdTemplate.name,
      is_default: false,
      is_active: true
    })
  });

  return createdTemplate.id;
}

export async function setPermissionTemplateActiveState({
  organizationId,
  actorUserId,
  templateId,
  isActive,
  reason
}: {
  organizationId: string;
  actorUserId: string;
  templateId: number;
  isActive: boolean;
  reason?: string | null;
}) {
  const template = await getPermissionTemplateById({ organizationId, templateId });

  if (template.is_system) {
    throw new Error('SYSTEM_TEMPLATE_PROTECTED');
  }

  await db
    .update(permissionTemplates)
    .set({
      isActive,
      updatedBy: actorUserId,
      updatedAt: new Date()
    })
    .where(eq(permissionTemplates.id, templateId));

  await logPermissionAuditEvent({
    organizationId,
    actorUserId,
    templateId,
    action: isActive
      ? PERMISSION_AUDIT_ACTION.TEMPLATE_ACTIVATE
      : PERMISSION_AUDIT_ACTION.TEMPLATE_DEACTIVATE,
    reason: reason ?? null,
    before: toPermissionAuditValue(template),
    after: toPermissionAuditValue({
      ...template,
      is_active: isActive
    })
  });
}

export async function softDeletePermissionTemplate({
  organizationId,
  actorUserId,
  templateId,
  reason
}: {
  organizationId: string;
  actorUserId: string;
  templateId: number;
  reason?: string | null;
}) {
  const template = await getPermissionTemplateById({ organizationId, templateId });

  if (template.is_system) {
    throw new Error('SYSTEM_TEMPLATE_PROTECTED');
  }

  await db
    .update(permissionTemplates)
    .set({
      isActive: false,
      deletedAt: new Date(),
      updatedBy: actorUserId,
      updatedAt: new Date()
    })
    .where(eq(permissionTemplates.id, templateId));

  await logPermissionAuditEvent({
    organizationId,
    actorUserId,
    templateId,
    action: PERMISSION_AUDIT_ACTION.TEMPLATE_SOFT_DELETE,
    reason: reason ?? null,
    before: toPermissionAuditValue(template),
    after: toPermissionAuditValue({
      ...template,
      is_active: false
    })
  });
}
