import { z } from 'zod';
import { ApiResponseSchema } from '#contracts/shared';
import { normalizePhone } from '#utils/stringUtils';

// ---------------------------------------------------------------------------
// Request schemas
// ---------------------------------------------------------------------------

export const LoginBodySchema = z
	.object({
		email: z.email().openapi({ example: 'user@example.com' }),
		password: z.string().min(1).max(128).openapi({ example: 'SecurePass123' }),
		device_info: z
			.record(z.string(), z.unknown())
			.optional()
			.openapi({
				example: {
					device_type: 'mobile',
					os: 'iOS 17.0',
					browser: 'Safari',
					model: 'iPhone 15 Pro',
				},
			}),
	})
	.openapi('LoginBody');

export const MobileLoginBodySchema = z
	.object({
		phone: z
			.string()
			.transform((val) => {
				const normalized = normalizePhone(val);
				if (!normalized) {
					throw new Error('Invalid phone number format');
				}
				return normalized;
			})
			.openapi({ example: '0901234567' }),
		password: z.string().min(1).max(128).openapi({ example: 'SecurePass123' }),
		device_info: z
			.record(z.string(), z.unknown())
			.optional()
			.openapi({
				example: {
					device_type: 'mobile',
					os: 'iOS 17.0',
					browser: 'Safari',
					model: 'iPhone 15 Pro',
				},
			}),
	})
	.openapi('MobileLoginBody');

export const WebLoginBodySchema = z
	.object({
		email: z.email().openapi({ example: 'user@example.com' }),
		password: z.string().min(1).max(128).openapi({ example: 'SecurePass123' }),
		device_info: z
			.record(z.string(), z.unknown())
			.optional()
			.openapi({
				example: {
					device_type: 'desktop',
					os: 'Windows 11',
					browser: 'Chrome 120',
					resolution: '1920x1080',
				},
			}),
	})
	.openapi('WebLoginBody');

export const RegisterBodySchema = z
	.object({
		email: z.email().openapi({ example: 'user@example.com' }),
		password: z.string().min(8).max(128).openapi({ example: 'SecurePass123' }),
		username: z.string().optional().openapi({ example: 'johndoe' }),
		first_name: z.string().optional().openapi({ example: 'John' }),
		last_name: z.string().optional().openapi({ example: 'Doe' }),
		phone: z
			.string()
			.regex(/^(0\d{9}|\+84\d{9}|\+?[1-9]\d{9,14})$/, 'Invalid phone number format')
			.optional()
			.openapi({ example: '0333748720' }),
	})
	.openapi('RegisterBody');

export const RefreshTokenBodySchema = z
	.object({
		refresh_token: z.string().optional().openapi({ example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' }),
		device_info: z
			.record(z.string(), z.unknown())
			.optional()
			.openapi({
				example: {
					device_type: 'mobile',
					os: 'iOS 17.0',
					browser: 'Safari',
				},
			}),
	})
	.openapi('RefreshTokenBody');

export const ChangePasswordBodySchema = z
	.object({
		old_password: z.string().min(1).max(128).openapi({ example: 'OldSecurePass123' }),
		new_password: z.string().min(8).max(128).openapi({ example: 'NewSecurePass456' }),
	})
	.openapi('ChangePasswordBody');

export const ChangePasswordResponseDataSchema = z
	.object({
		success: z.boolean(),
		message: z.string(),
	})
	.openapi('ChangePasswordResponseData');

export const ChangePasswordResponseSchema = ApiResponseSchema(ChangePasswordResponseDataSchema).openapi(
	'ChangePasswordResponse',
);
export type ChangePasswordResponse = z.infer<typeof ChangePasswordResponseSchema>;

// ---------------------------------------------------------------------------
// Response data schemas (the shape inside `responseData`)
// ---------------------------------------------------------------------------

/** Resident fields returned in profile */
export const ResidentProfileSchema = z
	.object({
		id: z.string().uuid().nullable().optional(),
		full_name: z.string().nullable().optional(),
		email: z.string().nullable().optional(),
		phone: z.string().nullable().optional(),
		id_card: z.string().nullable().optional(),
		date_of_birth: z.string().nullable().optional(),
		emergency_contact_name: z.string().nullable().optional(),
		emergency_contact_phone: z.string().nullable().optional(),
		emergency_contact_relationship: z.string().nullable().optional(),
		nationality: z.string().nullable().optional(),
		job: z.string().nullable().optional(),
		status: z.string().nullable().optional(),
	})
	.openapi('ResidentProfile');

/** Public user fields returned to the client — never includes password_hash, deleted_at, etc. */
export const UserPublicSchema = z
	.object({
		id: z.uuid(),
		email: z.email(),
		username: z.string().nullable(),
		first_name: z.string().nullable(),
		last_name: z.string().nullable(),
		status: z.string(),
		avatar_id: z.uuid().nullable().optional(),
		roles: z.array(z.string()),
		permissions: z.array(z.string()),
		last_login_at: z.string().nullable().optional(),
		resident_id: z.uuid().nullable().optional(),
		avatar_url: z.string().nullable().optional(),
		resident: ResidentProfileSchema.nullable().optional(),
	})
	.openapi('UserPublic');

export type UserPublic = z.infer<typeof UserPublicSchema>;

export const MobileLoginDataSchema = z
	.object({
		user: UserPublicSchema,
		access_token: z.string(),
		refresh_token: z.string(),
		token_type: z.literal('Bearer'),
	})
	.openapi('MobileLoginData');

export type MobileLoginData = z.infer<typeof MobileLoginDataSchema>;

export const RegisterResponseDataSchema = z
	.object({
		id: z.uuid(),
		email: z.email(),
		username: z.string().nullable(),
		status: z.string(),
	})
	.openapi('RegisterResponseData');

export type RegisterResponseData = z.infer<typeof RegisterResponseDataSchema>;

export const RefreshTokenDataSchema = z
	.object({
		session: z.object({
			expires_at: z.iso.datetime(),
			refresh_expires_at: z.iso.datetime(),
		}),
		// tokens only present in development mode
		access_token: z.string().optional(),
		refresh_token: z.string().optional(),
		token_type: z.literal('Bearer').optional(),
	})
	.openapi('RefreshTokenData');

// ---------------------------------------------------------------------------
// Full response schemas (envelope + data) — used by controllers with `satisfies`
// ---------------------------------------------------------------------------

export const LoginMobileResponseSchema = ApiResponseSchema(MobileLoginDataSchema).openapi('LoginMobileResponse');
export type LoginMobileResponse = z.infer<typeof LoginMobileResponseSchema>;

export const RegisterResponseSchema = ApiResponseSchema(RegisterResponseDataSchema).openapi('RegisterResponse');
export type RegisterResponse = z.infer<typeof RegisterResponseSchema>;

export const RefreshTokenResponseSchema = ApiResponseSchema(RefreshTokenDataSchema).openapi('RefreshTokenResponse');
export type RefreshTokenResponse = z.infer<typeof RefreshTokenResponseSchema>;

export const ProfileResponseSchema = ApiResponseSchema(UserPublicSchema).openapi('ProfileResponse');
export type ProfileResponse = z.infer<typeof ProfileResponseSchema>;

// ---------------------------------------------------------------------------
// Zalo login schemas
// ---------------------------------------------------------------------------

export const ZaloLoginBodySchema = z
	.object({
		zalo_code: z.string().optional().openapi({ example: 'zalo_auth_code_12345' }),
		zalo_open_id: z.string().optional().openapi({ example: '123456789' }),
		device_info: z
			.record(z.string(), z.unknown())
			.optional()
			.openapi({
				example: {
					device_type: 'zalo_mini_app',
					os: 'iOS',
					app_version: '1.0.0',
				},
			}),
	})
	.openapi('ZaloLoginBody');

export const ZaloLoginResponseSchema = ApiResponseSchema(MobileLoginDataSchema).openapi('ZaloLoginResponse');
export type ZaloLoginResponse = z.infer<typeof ZaloLoginResponseSchema>;

// ---------------------------------------------------------------------------
// OTP schemas
// ---------------------------------------------------------------------------

export const SendConfirmOtpResponseDataSchema = z
	.object({
		expiresInSeconds: z.number(),
		otp: z.string().optional(), // Only in development
	})
	.openapi('SendConfirmOtpResponseData');

export const SendConfirmOtpResponseSchema = ApiResponseSchema(SendConfirmOtpResponseDataSchema).openapi(
	'SendConfirmOtpResponse',
);
export type SendConfirmOtpResponse = z.infer<typeof SendConfirmOtpResponseSchema>;

// ---------------------------------------------------------------------------
// Session schemas
// ---------------------------------------------------------------------------

export const RevokeSessionResponseSchema = ApiResponseSchema(z.null()).openapi('RevokeSessionResponse');
export type RevokeSessionResponse = z.infer<typeof RevokeSessionResponseSchema>;

// ---------------------------------------------------------------------------
// Web login schemas
// ---------------------------------------------------------------------------

export const WebLoginDataSchema = z
	.object({
		user: UserPublicSchema,
		session: z.object({
			id: z.uuid(),
			expires_at: z.iso.datetime(),
			refresh_expires_at: z.iso.datetime(),
			expires_in: z.number(),
			refresh_expires_in: z.number(),
			token_type: z.literal('Bearer'),
		}),
		access_token: z.string().optional(),
		refresh_token: z.string().optional(),
	})
	.openapi('WebLoginData');

export const WebLoginResponseSchema = ApiResponseSchema(WebLoginDataSchema).openapi('WebLoginResponse');
export type WebLoginResponse = z.infer<typeof WebLoginResponseSchema>;

// ---------------------------------------------------------------------------
// Session list schema
// ---------------------------------------------------------------------------

export const SessionListResponseSchema = ApiResponseSchema(
	z.object({
		count: z.number(),
		rows: z.array(
			z.object({
				id: z.uuid(),
				platform: z.string(),
				device_id: z.string().nullable(),
				device_name: z.string().nullable(),
				ip_address: z.string().nullable(),
				user_agent: z.string().nullable(),
				last_activity_at: z.iso.datetime(),
				created_at: z.iso.datetime(),
				expires_at: z.iso.datetime(),
			}),
		),
		page: z.number(),
		pageSize: z.number(),
	}),
).openapi('SessionListResponse');
export type SessionListResponse = z.infer<typeof SessionListResponseSchema>;

// ---------------------------------------------------------------------------
// Forgot password schemas
// ---------------------------------------------------------------------------

export const PasswordResetRequestSchema = z
	.object({
		email: z.email(),
	})
	.openapi('PasswordResetRequest');

export const PasswordResetSubmitSchema = z
	.object({
		email: z.email(),
		code: z.string().length(6),
		new_password: z.string().min(8).max(128),
	})
	.openapi('PasswordResetSubmit');

export const PasswordResetSubmitResponseSchema = ApiResponseSchema(
	z.object({
		success: z.boolean(),
	}),
).openapi('PasswordResetSubmitResponse');
export type PasswordResetSubmitResponse = z.infer<typeof PasswordResetSubmitResponseSchema>;

export const PasswordResetRequestResponseDataSchema = z
	.object({
		sent: z.boolean(),
		expiresIn: z.number(),
	})
	.openapi('PasswordResetRequestResponseData');

export const PasswordResetRequestResponseSchema = ApiResponseSchema(PasswordResetRequestResponseDataSchema).openapi(
	'PasswordResetRequestResponse',
);
export type PasswordResetRequestResponse = z.infer<typeof PasswordResetRequestResponseSchema>;

// ---------------------------------------------------------------------------
// Email verification schemas
// ---------------------------------------------------------------------------

/**
 * Query string for GET /api/v1/auth/verify-email — the only thing the
 * public verification endpoint reads is the opaque token.
 */
export const VerifyEmailQuerySchema = z
	.object({
		token: z
			.string()
			.min(1)
			.max(512)
			.openapi({ example: 'a1b2c3d4...' }),
	})
	.openapi('VerifyEmailQuery');

/**
 * Response body for GET /api/v1/auth/verify-email on a fresh token.
 * `email_verified_at` is the new ISO timestamp the server just stamped
 * on the user record; `status` reflects the flip from
 * `pending_verification` to `active`.
 */
export const VerifyEmailResponseDataSchema = z
	.object({
		verified: z.literal(true),
		email: z.email(),
		status: z.string(),
		email_verified_at: z.iso.datetime(),
		message: z.string(),
	})
	.openapi('VerifyEmailResponseData');

export type VerifyEmailResponseData = z.infer<typeof VerifyEmailResponseDataSchema>;

export const VerifyEmailResponseSchema = ApiResponseSchema(VerifyEmailResponseDataSchema).openapi(
	'VerifyEmailResponse',
);
export type VerifyEmailResponse = z.infer<typeof VerifyEmailResponseSchema>;

/**
 * Body for POST /api/v1/auth/resend-verification. Email is the only field
 * we need; everything else is looked up server-side.
 */
export const ResendVerificationBodySchema = z
	.object({
		email: z.email().openapi({ example: 'user@example.com' }),
	})
	.openapi('ResendVerificationBody');

/**
 * Response body for POST /api/v1/auth/resend-verification. Always returns
 * `sent: true` regardless of whether the email exists, to avoid leaking
 * which addresses are registered. `expires_at` is null when no email
 * was actually dispatched (unknown user or already verified). `email_mode`
 * is the transport that handled the mail: 'smtp' in production, 'fallback'
 * when dev mode wrote to dev-mail.log.
 */
export const ResendVerificationResponseDataSchema = z
	.object({
		sent: z.literal(true),
		expires_at: z.iso.datetime().nullable(),
		email_mode: z.string(),
		message: z.string(),
	})
	.openapi('ResendVerificationResponseData');

export type ResendVerificationResponseData = z.infer<typeof ResendVerificationResponseDataSchema>;

export const ResendVerificationResponseSchema = ApiResponseSchema(ResendVerificationResponseDataSchema).openapi(
	'ResendVerificationResponse',
);
export type ResendVerificationResponse = z.infer<typeof ResendVerificationResponseSchema>;
