import Joi from 'joi';
import { MODULE_KEYS, PERMISSIONS, PROJECT_ROLES } from '../config/access.js';
import { gstinError, panError } from '../utils/taxIds.js';

// Bodies for /api/v1/admin/* (the tenant app's administration screens).
// Ids in bodies are base64url-encoded strings, like ids in URLs.

const encodedId = Joi.string().trim().max(64);
// "Clear this field": the apps send null or an empty string.
const optionalId = encodedId.allow(null, '');
const optionalText = (max: number) => Joi.string().trim().max(max).allow('', null);

// Runs a taxIds check as a Joi rule, surfacing its message.
const taxId = (check: (value: string) => string | null) =>
  Joi.string().trim().uppercase().allow('', null).custom((value: string, helpers) => {
    const error = check(value);
    return error ? helpers.message({ custom: error }) : value;
  });

export interface UpdateTenantOrganizationBody {
  /** Ignored: the logo is set through the upload endpoints. */
  logoUrl?: unknown;
  name?: string;
  legalName?: string | null;
  gstin?: string | null;
  pan?: string | null;
  address?: string | null;
  contactEmail?: string | null;
  timezone?: string;
  currency?: string;
  financialYearStartMonth?: number;
  id?: unknown;
}

export const updateTenantOrganizationSchema = Joi.object<UpdateTenantOrganizationBody>({
  name: Joi.string().trim().min(2).max(150),
  legalName: optionalText(200),
  gstin: taxId(gstinError),
  pan: taxId(panError),
  address: optionalText(500),
  // No top-level-domain allowlist: matches the tenant app's form, and new TLDs are valid.
  contactEmail: Joi.string().trim().lowercase().email({ tlds: { allow: false } }).max(254).allow('', null),
  // The logo is set through POST/DELETE /admin/organization/logo (S3); ignored here.
  logoUrl: Joi.any().strip(),
  timezone: Joi.string().trim().max(64),
  currency: Joi.string().trim().uppercase().length(3),
  financialYearStartMonth: Joi.number().integer().min(1).max(12),
  // The apps send the whole profile back; the id is read-only.
  id: Joi.any().strip(),
}).min(1).required();

export interface SubscriptionRequestBody {
  kind: 'seats' | 'modules' | 'other';
  message: string;
}

export const subscriptionRequestSchema = Joi.object<SubscriptionRequestBody>({
  kind: Joi.string().valid('seats', 'modules', 'other').required(),
  message: Joi.string().trim().min(1).max(2000).required(),
}).required();

export interface SetupBody {
  dismissed?: boolean;
  rolesReviewed?: boolean;
}

export const setupSchema = Joi.object<SetupBody>({
  dismissed: Joi.boolean(),
  rolesReviewed: Joi.boolean(),
}).min(1).required();

export interface DepartmentBody {
  name?: string;
  headId?: string | null;
  archived?: boolean;
}

export const createDepartmentSchema = Joi.object<DepartmentBody>({
  name: Joi.string().trim().min(2).max(100).required(),
  headId: optionalId,
}).required();

export const updateDepartmentSchema = Joi.object<DepartmentBody>({
  name: Joi.string().trim().min(2).max(100),
  headId: optionalId,
  archived: Joi.boolean(),
}).min(1).required();

export interface TeamBody {
  departmentId?: string;
  name?: string;
  leadId?: string | null;
}

export const createTeamSchema = Joi.object<TeamBody>({
  departmentId: encodedId.required(),
  name: Joi.string().trim().min(2).max(100).required(),
  leadId: optionalId,
}).required();

export const updateTeamSchema = Joi.object<TeamBody>({
  name: Joi.string().trim().min(2).max(100),
  leadId: optionalId,
}).min(1).required();

export interface UpdateTenantUserBody {
  name?: string;
  phone?: string | null;
  jobTitle?: string | null;
  roleIds?: string[];
  departmentId?: string | null;
  teamId?: string | null;
  id?: unknown;
}

export const updateTenantUserSchema = Joi.object<UpdateTenantUserBody>({
  name: Joi.string().trim().min(2).max(150),
  phone: optionalText(30),
  jobTitle: optionalText(100),
  roleIds: Joi.array().items(encodedId).min(1).unique()
    .messages({ 'array.min': 'Keep at least one role' }),
  departmentId: optionalId,
  teamId: optionalId,
  // The apps send the id in the body too; the URL decides which user.
  id: Joi.any().strip(),
}).min(1).required();

export interface UserStatusBody {
  status: 'active' | 'disabled';
}

export const userStatusSchema = Joi.object<UserStatusBody>({
  status: Joi.string().valid('active', 'disabled').required(),
}).required();

export interface RoleBody {
  name: string;
  description: string;
  scope: 'all' | 'assigned';
  permissions: string[];
  modules: string[];
  clonedFromId?: string | null;
  id?: unknown;
  isSystem?: unknown;
  userCount?: unknown;
}

const permissionCodes = PERMISSIONS.map((p) => p.name);

export const roleSchema = Joi.object<RoleBody>({
  name: Joi.string().trim().min(2).max(60).required().messages({ 'string.min': 'Name the role' }),
  description: Joi.string().trim().max(300).allow('').default(''),
  scope: Joi.string().valid('all', 'assigned').required(),
  permissions: Joi.array().items(Joi.string().valid(...permissionCodes)).unique().required(),
  modules: Joi.array().items(Joi.string().valid(...MODULE_KEYS)).unique().required(),
  clonedFromId: optionalId,
  // Read-only fields the apps may send back with the role.
  id: Joi.any().strip(),
  isSystem: Joi.any().strip(),
  userCount: Joi.any().strip(),
}).required();

// { [encoded roleId]: ModuleKey[] }
export const moduleAccessSchema = Joi.object()
  .pattern(encodedId, Joi.array().items(Joi.string().valid(...MODULE_KEYS)).unique())
  .min(1)
  .required();

export interface InviteUsersBody {
  emails: string[];
  roleIds: string[];
  departmentId?: string | null;
  teamId?: string | null;
  projectIds: string[];
}

export const inviteUsersSchema = Joi.object<InviteUsersBody>({
  emails: Joi.array()
    .items(Joi.string().trim().lowercase().email({ tlds: { allow: false } }).max(254))
    .min(1).max(50).required()
    .messages({ 'array.min': 'Add at least one email', 'string.email': 'Not a valid email: {#value}' }),
  roleIds: Joi.array().items(encodedId).min(1).unique().required().messages({ 'array.min': 'Choose at least one role' }),
  departmentId: optionalId,
  teamId: optionalId,
  projectIds: Joi.array().items(encodedId).unique().default([]),
}).required();

export interface AcceptInviteBody {
  name: string;
  password: string;
}

// Same rule as the invite page: at least 8 characters, including a number.
export const acceptInviteSchema = Joi.object<AcceptInviteBody>({
  name: Joi.string().trim().min(2).max(150).required().messages({ 'string.min': 'Enter your name' }),
  password: Joi.string().min(8).max(72).pattern(/\d/).required()
    .messages({ 'string.min': 'Use at least 8 characters, including a number', 'string.pattern.base': 'Use at least 8 characters, including a number' }),
}).required();

const projectRole = Joi.string().valid(...PROJECT_ROLES).required();
// Empty = every site of the project.
const siteIds = Joi.array().items(encodedId).unique().default([]);

// Projects & sites setup. Codes are short, upper-case identifiers (e.g. "EF-TWR").
export const projectSetupSchema = Joi.object({
  name: Joi.string().trim().min(2).max(120).required(),
  code: Joi.string().trim().uppercase().pattern(/^[A-Z0-9]+(-[A-Z0-9]+)*$/).min(2).max(20).required()
    .messages({ 'string.pattern.base': 'Use letters, numbers and dashes, e.g. EF-TWR' }),
  location: optionalText(160),
  sites: Joi.array()
    .items(Joi.object({ id: encodedId, name: Joi.string().trim().min(1).max(80).required() }))
    .max(100)
    .unique((a, b) => a.name.toLowerCase() === b.name.toLowerCase())
    .default([])
    .messages({ 'array.unique': 'Each site needs a different name' }),
  projectId: Joi.any().strip(),
}).required();

export const projectMembersSchema = Joi.object({
  userIds: Joi.array().items(encodedId).min(1).unique().required().messages({ 'array.min': 'Choose at least one person' }),
  projectRole,
  siteIds,
  projectId: Joi.any().strip(),
}).required();

export const updateProjectMemberSchema = Joi.object({
  projectRole,
  siteIds,
  projectId: Joi.any().strip(),
  userId: Joi.any().strip(),
}).required();

export interface AccessRequestBody {
  module?: string;
  path: string;
  message?: string;
}

export const accessRequestSchema = Joi.object<AccessRequestBody>({
  module: Joi.string().valid(...MODULE_KEYS),
  path: Joi.string().trim().max(300).required(),
  message: Joi.string().trim().max(1000).allow(''),
}).required();
