import { eq } from "drizzle-orm";
import { getDb } from "@/core/db/client";
import {
  architectProfiles,
  domains,
  plans,
  subscriptions,
  tenantUsers,
  tenants,
  users,
} from "@/core/db/schema";
import {
  isValidTenantSlug,
  normalizeSlug,
  suggestSlugAlternatives,
} from "@/core/tenant/slug";
import { getCanonicalTenantSubdomain } from "@/core/config/domains";
import { permissionsForRole } from "@/core/authorization/authorize";
import type { createTenantSchema } from "@/core/validation/schemas";
import type { z } from "zod";
import { hash } from "bcryptjs";

type CreateTenantInput = z.infer<typeof createTenantSchema> & {
  ownerUserId: string;
};

type RegisterStudioInput = {
  name: string;
  email: string;
  password: string;
  studioName: string;
  slug?: string;
};

/**
 * Transactional tenant creation.
 * Creates tenant + membership + default profile + subdomain domain row.
 */
export async function createTenantWithOwner(input: CreateTenantInput) {
  const db = getDb();
  const preferred =
    (input.slug && normalizeSlug(input.slug)) ||
    normalizeSlug(input.studioName);

  if (!isValidTenantSlug(preferred)) {
    const alternatives = suggestSlugAlternatives(preferred, input.studioName);
    throw Object.assign(new Error("Slug unavailable or invalid"), {
      name: "SlugConflictError",
      alternatives,
    });
  }

  const existing = await db.query.tenants.findFirst({
    where: eq(tenants.slug, preferred),
  });

  if (existing) {
    throw Object.assign(new Error("Slug already taken"), {
      name: "SlugConflictError",
      alternatives: suggestSlugAlternatives(preferred, input.studioName),
    });
  }

  return db.transaction(async (tx) => {
    const [tenant] = await tx
      .insert(tenants)
      .values({
        studioName: input.studioName.trim(),
        slug: preferred,
        status: "active",
      })
      .returning();

    if (!tenant) {
      throw new Error("Failed to create tenant");
    }

    await tx.insert(tenantUsers).values({
      tenantId: tenant.id,
      userId: input.ownerUserId,
      role: "owner",
      permissions: permissionsForRole("owner"),
      status: "active",
    });

    await tx.insert(architectProfiles).values({
      tenantId: tenant.id,
      tagline: "",
      about: "",
    });

    const subdomain = getCanonicalTenantSubdomain(tenant.slug);
    await tx.insert(domains).values({
      tenantId: tenant.id,
      domain: subdomain,
      normalizedDomain: subdomain,
      type: "subdomain",
      verificationStatus: "verified",
      sslStatus: "active",
      isPrimary: true,
      verifiedAt: new Date(),
    });

    const starterPlan = await tx.query.plans.findFirst({
      where: eq(plans.code, "starter"),
    });

    if (starterPlan) {
      await tx.insert(subscriptions).values({
        tenantId: tenant.id,
        planId: starterPlan.id,
        status: "trialing",
        currentPeriodStart: new Date(),
        currentPeriodEnd: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000),
      });
    }

    return tenant;
  });
}

/**
 * Full onboarding: user + tenant + ownership in one transaction.
 */
export async function registerStudio(input: RegisterStudioInput) {
  const db = getDb();
  const email = input.email.trim().toLowerCase();

  const existingUser = await db.query.users.findFirst({
    where: eq(users.email, email),
  });
  if (existingUser) {
    throw Object.assign(new Error("Email already registered"), {
      name: "ConflictError",
    });
  }

  const passwordHash = await hash(input.password, 12);

  return db.transaction(async (tx) => {
    const [user] = await tx
      .insert(users)
      .values({
        name: input.name.trim(),
        email,
        passwordHash,
        status: "pending_verification",
        platformRole: "none",
      })
      .returning();

    if (!user) {
      throw new Error("Failed to create user");
    }

    // Nested service uses getDb(); for true single-transaction we inline create
    const preferred =
      (input.slug && normalizeSlug(input.slug)) ||
      normalizeSlug(input.studioName);

    if (!isValidTenantSlug(preferred)) {
      throw Object.assign(new Error("Slug unavailable or invalid"), {
        name: "SlugConflictError",
        alternatives: suggestSlugAlternatives(preferred, input.studioName),
      });
    }

    const existing = await tx.query.tenants.findFirst({
      where: eq(tenants.slug, preferred),
    });
    if (existing) {
      throw Object.assign(new Error("Slug already taken"), {
        name: "SlugConflictError",
        alternatives: suggestSlugAlternatives(preferred, input.studioName),
      });
    }

    const [tenant] = await tx
      .insert(tenants)
      .values({
        studioName: input.studioName.trim(),
        slug: preferred,
        status: "active",
      })
      .returning();

    if (!tenant) {
      throw new Error("Failed to create tenant");
    }

    await tx.insert(tenantUsers).values({
      tenantId: tenant.id,
      userId: user.id,
      role: "owner",
      permissions: permissionsForRole("owner"),
      status: "active",
    });

    await tx.insert(architectProfiles).values({ tenantId: tenant.id });

    const subdomain = getCanonicalTenantSubdomain(tenant.slug);
    await tx.insert(domains).values({
      tenantId: tenant.id,
      domain: subdomain,
      normalizedDomain: subdomain,
      type: "subdomain",
      verificationStatus: "verified",
      sslStatus: "active",
      isPrimary: true,
      verifiedAt: new Date(),
    });

    return { user, tenant };
  });
}
