import React, { useState } from 'react';
import { Modal, Button, Typography, message, Tooltip } from 'antd';
import { CopyOutlined, CheckOutlined } from '@ant-design/icons';

const { Text } = Typography;

interface SqlModalProps {
  open: boolean;
  onClose: () => void;
  title: string;
  sql: string;
}

export const SqlModal: React.FC<SqlModalProps> = ({
  open,
  onClose,
  title,
  sql,
}) => {
  const [copied, setCopied] = useState(false);

  const handleCopy = () => {
    navigator.clipboard.writeText(sql).then(() => {
      setCopied(true);
      message.success('SQL copied to clipboard');
      setTimeout(() => setCopied(false), 2000);
    });
  };

  return (
    <Modal
      title={title}
      open={open}
      onCancel={onClose}
      width={720}
      footer={[
        <Tooltip key="copy" title={copied ? 'Copied!' : 'Copy SQL'}>
          <Button
            icon={copied ? <CheckOutlined /> : <CopyOutlined />}
            type="primary"
            onClick={handleCopy}
          >
            {copied ? 'Copied!' : 'Copy SQL'}
          </Button>
        </Tooltip>,
        <Button key="close" onClick={onClose}>
          Close
        </Button>,
      ]}
    >
      <pre
        style={{
          background: '#1e1e1e',
          color: '#d4d4d4',
          padding: '16px',
          borderRadius: '6px',
          overflowX: 'auto',
          overflowY: 'auto',
          maxHeight: '500px',
          fontSize: '12px',
          lineHeight: '1.6',
          whiteSpace: 'pre-wrap',
          wordBreak: 'break-word',
          fontFamily:
            "'Fira Code', 'Cascadia Code', 'Consolas', 'Monaco', monospace",
        }}
      >
        <Text
          style={{
            color: 'inherit',
            fontSize: 'inherit',
            fontFamily: 'inherit',
          }}
        >
          {sql}
        </Text>
      </pre>
    </Modal>
  );
};

// ─── SQL Generators ────────────────────────────────────────────────────────────

function escapeStr(val: string | null | undefined): string {
  if (val == null) return 'NULL';
  return `'${String(val).replace(/'/g, "''")}'`;
}

function jsonArr(val: string | null | undefined): string {
  // roles is stored as a JSON-encoded array string in the DB, e.g. '["GUEST","PRODUCT"]'
  if (val == null) return "'{}'";
  return `'${String(val).replace(/'/g, "''")}'`;
}

/**
 * Generate an INSERT for t_admin_audit_log.
 * authorId / authorName come from the currently logged-in user passed by the caller.
 */
function auditInsert(opts: {
  action: string;
  resource: string;
  resourceId: string;
  data: string; // JSON string of the record
  creator?: string | null;
  authorId?: string | null;
  authorName?: string | null;
}): string {
  const dataJson = escapeStr(opts.data);
  const authorId = opts.authorId ?? '<author_id>';
  const authorName = opts.authorName ?? '<author_name>';
  return `INSERT INTO public.t_admin_audit_log (creator, "authorId", "authorName", action, resource, "resourceId", data, "previousData")
VALUES (
  ${escapeStr(authorId)},
  ${escapeStr(authorId)},
  ${escapeStr(authorName)},
  ${escapeStr(opts.action)},
  ${escapeStr(opts.resource)},
  ${escapeStr(opts.resourceId)},
  ${dataJson},
  NULL
);`;
}

/**
 * Generate INSERT SQL for a User record.
 * Table: t_admin_user_role
 */
export function generateUserSql(
  user: {
    userid: string;
    username?: string | null;
    roles?: string | null;
    isApiKey?: boolean;
    apiKey?: string | null;
    description?: string | null;
    isActive?: boolean;
    creator?: string | null;
  },
  operator?: { authorId?: string | null; authorName?: string | null },
): string {
  const rolesValue = user.roles ?? '[]';
  const isApiKey = user.isApiKey ? 'true' : 'false';
  const isActive = user.isActive !== false ? 'true' : 'false';

  const userDataJson = JSON.stringify({
    userid: user.userid,
    username: user.username ?? null,
    roles: rolesValue,
    isApiKey: user.isApiKey ?? false,
    description: user.description ?? null,
    isActive: user.isActive !== false,
  });

  const mainSql = `-- Insert User: ${user.userid}
INSERT INTO public.t_admin_user_role (userid, username, roles, "isApiKey", "apiKey", description, "isActive", creator, "createdAt", "updatedAt")
VALUES (
  ${escapeStr(user.userid)},
  ${escapeStr(user.username)},
  ${jsonArr(rolesValue)},
  ${isApiKey},
  ${user.apiKey ? escapeStr(user.apiKey) : 'NULL'},
  ${escapeStr(user.description)},
  ${isActive},
  ${escapeStr(user.creator)},
  NOW(),
  NOW()
)
ON CONFLICT (userid) DO UPDATE SET
  username    = EXCLUDED.username,
  roles       = EXCLUDED.roles,
  "isApiKey"  = EXCLUDED."isApiKey",
  "apiKey"    = EXCLUDED."apiKey",
  description = EXCLUDED.description,
  "isActive"  = EXCLUDED."isActive",
  "updatedAt" = NOW();`;

  return [
    mainSql,
    '',
    auditInsert({
      action: 'create',
      resource: 'user',
      resourceId: user.userid,
      data: userDataJson,
      creator: user.creator,
      ...operator,
    }),
  ].join('\n');
}

/**
 * Generate INSERT SQL for a Resource record.
 * Table: t_resource
 */
export function generateResourceSql(
  resource: {
    code: string;
    name: string;
    description?: string | null;
    isCommonResource?: boolean;
    creator?: string | null;
  },
  operator?: { authorId?: string | null; authorName?: string | null },
): string {
  const isCommon = resource.isCommonResource ? 'true' : 'false';

  const resourceDataJson = JSON.stringify({
    code: resource.code,
    name: resource.name,
    description: resource.description ?? null,
    isCommonResource: resource.isCommonResource ?? false,
  });

  const mainSql = `-- Insert Resource: ${resource.code}
INSERT INTO public.t_resource (code, name, description, is_common_resource, creator, "createdAt", "updatedAt")
VALUES (
  ${escapeStr(resource.code)},
  ${escapeStr(resource.name)},
  ${escapeStr(resource.description)},
  ${isCommon},
  ${escapeStr(resource.creator)},
  NOW(),
  NOW()
)
ON CONFLICT (code) DO UPDATE SET
  name               = EXCLUDED.name,
  description        = EXCLUDED.description,
  is_common_resource = EXCLUDED.is_common_resource,
  "updatedAt"        = NOW();`;

  return [
    mainSql,
    '',
    auditInsert({
      action: 'create',
      resource: 'resource',
      resourceId: resource.code,
      data: resourceDataJson,
      creator: resource.creator,
      ...operator,
    }),
  ].join('\n');
}

/**
 * Generate INSERT SQL for a Role record, including its page assignments and
 * the resulting role_resource entries.
 *
 * rolePages: list of { pageId, pageName }
 * roleResources: list of { resourceCode, resourceName, actions: string[] }
 */
export function generateRoleSql(
  role: {
    name: string;
    description?: string | null;
    creator?: string | null;
  },
  rolePages: Array<{ pageId: number; pageName: string; pageCode: string }>,
  roleResources: Array<{
    resourceCode: string;
    resourceName: string;
    actions: string[];
  }>,
  operator?: { authorId?: string | null; authorName?: string | null },
): string {
  const lines: string[] = [];

  // 1. Role
  lines.push(`-- ════════════════════════════════════════════════════════`);
  lines.push(`-- Role: ${role.name}`);
  lines.push(`-- ════════════════════════════════════════════════════════`);
  lines.push(
    `INSERT INTO public.t_role (name, description, creator, "createdAt", "updatedAt")`,
  );
  lines.push(`VALUES (`);
  lines.push(`  ${escapeStr(role.name)},`);
  lines.push(`  ${escapeStr(role.description)},`);
  lines.push(`  ${escapeStr(role.creator)},`);
  lines.push(`  NOW(),`);
  lines.push(`  NOW()`);
  lines.push(`)`);
  lines.push(`ON CONFLICT (name) DO UPDATE SET`);
  lines.push(`  description = EXCLUDED.description,`);
  lines.push(`  "updatedAt" = NOW();`);

  // 2. Role-page associations
  if (rolePages.length > 0) {
    lines.push(``);
    lines.push(`-- Role-Page Associations for "${role.name}"`);
    lines.push(`-- Pages: ${rolePages.map((p) => p.pageName).join(', ')}`);

    for (const rp of rolePages) {
      lines.push(``);
      lines.push(
        `INSERT INTO public.t_role_pages (role_id, page_id, "created_at", "updated_at")`,
      );
      lines.push(`SELECT r.id, p.id, NOW(), NOW()`);
      lines.push(`FROM public.t_role r, public.t_page p`);
      lines.push(`WHERE r.name = ${escapeStr(role.name)}`);
      lines.push(`  AND p.code = ${escapeStr(rp.pageCode)} -- ${rp.pageName}`);
      lines.push(`ON CONFLICT (role_id, page_id) DO NOTHING;`);
    }
  }

  // 3. Role-resource associations
  if (roleResources.length > 0) {
    lines.push(``);
    lines.push(`-- Role-Resource Permissions for "${role.name}"`);

    for (const rr of roleResources) {
      const actionsJson = JSON.stringify(rr.actions);
      lines.push(``);
      lines.push(
        `INSERT INTO public.t_role_resource (role_id, resource_id, actions, "created_at", "updated_at")`,
      );
      lines.push(`SELECT r.id, res.id, '${actionsJson}'::jsonb, NOW(), NOW()`);
      lines.push(`FROM public.t_role r, public.t_resource res`);
      lines.push(`WHERE r.name = ${escapeStr(role.name)}`);
      lines.push(
        `  AND res.code = ${escapeStr(rr.resourceCode)} -- ${rr.resourceName}`,
      );
      lines.push(`ON CONFLICT (role_id, resource_id) DO UPDATE SET`);
      lines.push(`  actions     = EXCLUDED.actions,`);
      lines.push(`  "updated_at" = NOW();`);
    }
  }

  // 4. Audit log
  const roleDataJson = JSON.stringify({
    name: role.name,
    description: role.description ?? null,
  });
  lines.push(``);
  lines.push(
    auditInsert({
      action: 'create',
      resource: 'role',
      resourceId: role.name,
      data: roleDataJson,
      creator: role.creator,
      ...operator,
    }),
  );

  return lines.join('\n');
}

/**
 * Generate INSERT SQL for a Page and (optionally) its children.
 *
 * When called on a parent page, pass all child pages in `children`.
 * Each page includes its pageResources (resource assignments).
 *
 * pageResources: list of { resourceCode, resourceName, actions }
 */
export function generatePageSql(
  page: {
    id: number;
    code: string;
    name: string;
    path?: string | null;
    description?: string | null;
    zhName?: string | null;
    enName?: string | null;
    sortOrder?: number | null;
    parentId?: number | null;
    /** code of the parent page — used to generate a portable subquery instead of a raw numeric ID */
    parentCode?: string | null;
    isVirtual?: boolean;
    creator?: string | null;
  },
  pageResources: Array<{
    resourceCode: string;
    resourceName: string;
    actions: string[];
  }>,
  children: Array<{
    page: typeof page;
    pageResources: Array<{
      resourceCode: string;
      resourceName: string;
      actions: string[];
    }>;
  }>,
  operator?: { authorId?: string | null; authorName?: string | null },
): string {
  const lines: string[] = [];

  const renderPageInsert = (
    p: typeof page,
    res: Array<{
      resourceCode: string;
      resourceName: string;
      actions: string[];
    }>,
    parentRef: string | null,
  ) => {
    lines.push(`-- ────────────────────────────────────────────────────────`);
    lines.push(`-- Page: ${p.name} (code: ${p.code})`);
    lines.push(`-- ────────────────────────────────────────────────────────`);
    lines.push(`INSERT INTO public.t_page (`);
    lines.push(
      `  code, name, path, description, zh_name, en_name, sort_order, parent_id, "isVirtual", creator, created_at, updated_at`,
    );
    lines.push(`)`);
    lines.push(`VALUES (`);
    lines.push(`  ${escapeStr(p.code)},`);
    lines.push(`  ${escapeStr(p.name)},`);
    lines.push(`  ${escapeStr(p.path)},`);
    lines.push(`  ${escapeStr(p.description)},`);
    lines.push(`  ${escapeStr(p.zhName)},`);
    lines.push(`  ${escapeStr(p.enName)},`);
    lines.push(`  ${p.sortOrder != null ? p.sortOrder : 'NULL'},`);
    if (parentRef) {
      lines.push(`  (${parentRef}), -- parent_id resolved by code`);
    } else {
      lines.push(`  NULL,`);
    }
    lines.push(`  ${p.isVirtual ? 'true' : 'false'},`);
    lines.push(`  ${escapeStr(p.creator)},`);
    lines.push(`  NOW(),`);
    lines.push(`  NOW()`);
    lines.push(`)`);
    lines.push(`ON CONFLICT (code) DO UPDATE SET`);
    lines.push(`  name        = EXCLUDED.name,`);
    lines.push(`  path        = EXCLUDED.path,`);
    lines.push(`  description = EXCLUDED.description,`);
    lines.push(`  zh_name     = EXCLUDED.zh_name,`);
    lines.push(`  en_name     = EXCLUDED.en_name,`);
    lines.push(`  sort_order  = EXCLUDED.sort_order,`);
    lines.push(`  parent_id   = EXCLUDED.parent_id,`);
    lines.push(`  updated_at  = NOW();`);

    if (res.length > 0) {
      lines.push(``);
      lines.push(`-- Page-Resource Associations for "${p.name}"`);
      for (const pr of res) {
        const actionsJson = JSON.stringify(pr.actions);
        lines.push(``);
        lines.push(
          `INSERT INTO public.t_page_resource (page_id, resource_id, actions, "created_at", "updated_at")`,
        );
        lines.push(
          `SELECT pg.id, res.id, '${actionsJson}'::jsonb, NOW(), NOW()`,
        );
        lines.push(`FROM public.t_page pg, public.t_resource res`);
        lines.push(`WHERE pg.code = ${escapeStr(p.code)}`);
        lines.push(
          `  AND res.code = ${escapeStr(pr.resourceCode)} -- ${pr.resourceName}`,
        );
        lines.push(`ON CONFLICT (page_id, resource_id) DO UPDATE SET`);
        lines.push(`  actions      = EXCLUDED.actions,`);
        lines.push(`  "updated_at" = NOW();`);
      }
    }

    // Audit log for this page
    const pageDataJson = JSON.stringify({
      code: p.code,
      name: p.name,
      path: p.path ?? null,
      description: p.description ?? null,
      zhName: p.zhName ?? null,
      enName: p.enName ?? null,
      sortOrder: p.sortOrder ?? null,
      isVirtual: p.isVirtual ?? false,
    });
    lines.push(``);
    lines.push(
      auditInsert({
        action: 'create',
        resource: 'page',
        resourceId: p.code,
        data: pageDataJson,
        creator: p.creator,
        ...operator,
      }),
    );
  };

  // Parent page — if it has a parentCode, resolve parent_id dynamically by code
  const rootParentRef = page.parentCode
    ? `SELECT id FROM public.t_page WHERE code = ${escapeStr(page.parentCode)}`
    : null;
  renderPageInsert(page, pageResources, rootParentRef);

  // Children
  for (const child of children) {
    lines.push(``);
    const parentRef = `SELECT id FROM public.t_page WHERE code = ${escapeStr(page.code)}`;
    renderPageInsert(child.page, child.pageResources, parentRef);
  }

  return lines.join('\n');
}
