import * as plugins from '../../../plugins.js';
import * as appstate from '../../../appstate.js';
import { viewHostCss } from '../../shared/index.js';
import {
  DeesElement,
  customElement,
  html,
  state,
  css,
  cssManager,
  type TemplateResult,
} from '@design.estate/dees-element';

@customElement('gitops-view-sync')
export class GitopsViewSync extends DeesElement {
  @state()
  accessor syncState: appstate.ISyncState = { configs: [], repoStatuses: [] };

  @state()
  accessor connectionsState: appstate.IConnectionsState = {
    connections: [],
    activeConnectionId: null,
  };

  private _autoRefreshHandler: () => void;
  private _syncLogHandler: (e: Event) => void;

  constructor() {
    super();
    const syncSub = appstate.syncStatePart
      .select((s) => s)
      .subscribe((s) => { this.syncState = s; });
    this.rxSubscriptions.push(syncSub);

    const connSub = appstate.connectionsStatePart
      .select((s) => s)
      .subscribe((s) => { this.connectionsState = s; });
    this.rxSubscriptions.push(connSub);

    this._autoRefreshHandler = () => this.refresh();
    document.addEventListener('gitops-auto-refresh', this._autoRefreshHandler);

    // Listen for server-push sync log entries via TypedSocket
    this._syncLogHandler = (e: Event) => {
      const entry = (e as CustomEvent).detail;
      if (!entry) return;
      const chartLog = this.shadowRoot?.querySelector('dees-chart-log') as any;
      if (chartLog?.addLog) {
        chartLog.addLog(entry.level, entry.message, entry.source);
      }
    };
    document.addEventListener('gitops-sync-log-entry', this._syncLogHandler);
  }

  public override disconnectedCallback() {
    super.disconnectedCallback();
    document.removeEventListener('gitops-auto-refresh', this._autoRefreshHandler);
    document.removeEventListener('gitops-sync-log-entry', this._syncLogHandler);
  }

  public static styles = [
    cssManager.defaultStyles,
    viewHostCss,
    css`
      .status-badge {
        display: inline-block;
        padding: 2px 8px;
        border-radius: 4px;
        font-size: 12px;
        font-weight: 600;
        text-transform: uppercase;
      }
      .status-active { background: #1a3a1a; color: #00ff88; }
      .status-paused { background: #3a3a1a; color: #ffaa00; }
      .status-error { background: #3a1a1a; color: #ff4444; }

      dees-chart-log {
        margin-top: 24px;
      }
    `,
  ];

  public render(): TemplateResult {
    return html`
      <div class="view-title">Sync</div>
      <div class="view-description">Mirror repositories between Gitea and GitLab instances</div>
      <div class="toolbar">
        <dees-button @click=${() => this.addSyncConfig()}>Add Sync</dees-button>
        <dees-button @click=${() => this.refresh()}>Refresh</dees-button>
      </div>
      <dees-table
        .heading1=${'Sync Configurations'}
        .heading2=${'Automatic repository mirroring between instances'}
        .data=${this.syncState.configs}
        .displayFunction=${(item: any) => {
          const sourceConn = this.connectionsState.connections.find((c) => c.id === item.sourceConnectionId);
          const targetConn = this.connectionsState.connections.find((c) => c.id === item.targetConnectionId);
          return {
            Name: item.name,
            Source: sourceConn?.name || item.sourceConnectionId,
            'Target': `${targetConn?.name || item.targetConnectionId}${item.targetGroupOffset ? ` → ${item.targetGroupOffset}/` : ''}`,
            Interval: `${item.intervalMinutes}m`,
            Status: item.status,
            'Enforce Delete': item.enforceDelete ? 'Yes' : 'No',
            'Enforce Group Delete': item.enforceGroupDelete ? 'Yes' : 'No',
            'Mirror Hint': item.addMirrorHint ? 'Yes' : 'No',
            'Group Avatars': item.useGroupAvatarsForProjects ? 'Yes' : 'No',
            'Last Sync': item.lastSyncAt ? new Date(item.lastSyncAt).toLocaleString() : 'Never',
            Repos: String(item.reposSynced),
          };
        }}
        .dataActions=${[
          {
            name: 'Preview',
            iconName: 'lucide:eye',
            type: ['inRow', 'contextmenu'],
            actionFunc: async ({ item }: any) => { await this.previewSync(item); },
          },
          {
            name: 'Trigger Now',
            iconName: 'lucide:play',
            type: ['inRow', 'contextmenu'],
            actionFunc: async ({ item }: any) => {
              const statusNote = item.status === 'paused' ? ' (config is paused — this is a one-off run)' : '';
              await plugins.deesCatalog.DeesModal.createAndShow({
                heading: 'Trigger Sync',
                content: html`<p style="color: #fff;">Run sync "${item.name}" now?${statusNote}</p>`,
                menuOptions: [
                  { name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
                  {
                    name: 'Trigger',
                    action: async (modal: any) => {
                      await appstate.syncStatePart.dispatchAction(appstate.triggerSyncAction, {
                        syncConfigId: item.id,
                      });
                      modal.destroy();
                    },
                  },
                ],
              });
            },
          },
          {
            name: 'View Repos',
            iconName: 'lucide:list',
            type: ['inRow', 'contextmenu'],
            actionFunc: async ({ item }: any) => { await this.viewRepoStatuses(item); },
          },
          {
            name: 'Edit',
            iconName: 'lucide:edit',
            type: ['inRow', 'contextmenu'],
            actionFunc: async ({ item }: any) => { await this.editSyncConfig(item); },
          },
          {
            name: 'Pause/Resume',
            iconName: 'lucide:pauseCircle',
            type: ['inRow', 'contextmenu'],
            actionFunc: async ({ item }: any) => {
              const isPaused = item.status === 'paused';
              const actionLabel = isPaused ? 'Resume' : 'Pause';
              await plugins.deesCatalog.DeesModal.createAndShow({
                heading: `${actionLabel} Sync`,
                content: html`<p style="color: #fff;">Are you sure you want to ${actionLabel.toLowerCase()} sync "${item.name}"?</p>`,
                menuOptions: [
                  { name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
                  {
                    name: actionLabel,
                    action: async (modal: any) => {
                      await appstate.syncStatePart.dispatchAction(appstate.pauseSyncConfigAction, {
                        syncConfigId: item.id,
                        paused: !isPaused,
                      });
                      modal.destroy();
                    },
                  },
                ],
              });
            },
          },
          {
            name: 'Delete',
            iconName: 'lucide:trash2',
            type: ['inRow', 'contextmenu'],
            actionFunc: async ({ item }: any) => {
              await plugins.deesCatalog.DeesModal.createAndShow({
                heading: 'Delete Sync Config',
                content: html`<p style="color: #fff;">Are you sure you want to delete sync config "${item.name}"? This will also remove all local mirror data.</p>`,
                menuOptions: [
                  { name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
                  {
                    name: 'Delete',
                    action: async (modal: any) => {
                      await appstate.syncStatePart.dispatchAction(appstate.deleteSyncConfigAction, {
                        syncConfigId: item.id,
                      });
                      modal.destroy();
                    },
                  },
                ],
              });
            },
          },
        ]}
      ></dees-table>
      <dees-chart-log
        .label=${'Sync Activity Log'}
        .autoScroll=${true}
        .maxEntries=${500}
      ></dees-chart-log>
    `;
  }

  async firstUpdated() {
    await appstate.connectionsStatePart.dispatchAction(appstate.fetchConnectionsAction, null);
    await this.refresh();
    // Initialize TypedSocket for server-push sync log entries
    await appstate.initSyncLogSocket();
    // Load existing log entries
    await this.loadExistingLogs();
  }

  private async loadExistingLogs() {
    try {
      const logs = await appstate.fetchSyncLogs(200);
      const chartLog = this.shadowRoot?.querySelector('dees-chart-log') as any;
      if (chartLog?.updateLog && logs.length > 0) {
        chartLog.updateLog(
          logs.map((entry) => ({
            timestamp: new Date(entry.timestamp).toISOString(),
            level: entry.level,
            message: entry.message,
            source: entry.source,
          })),
        );
      }
    } catch (err) {
      console.error('Failed to load sync logs:', err);
    }
  }

  private async refresh() {
    await appstate.syncStatePart.dispatchAction(appstate.fetchSyncConfigsAction, null);
  }

  private async addSyncConfig() {
    const connectionOptions = this.connectionsState.connections.map((c) => ({
      option: `${c.name} (${c.providerType})`,
      key: c.id,
    }));

    await plugins.deesCatalog.DeesModal.createAndShow({
      heading: 'Add Sync Configuration',
      content: html`
        <style>.form-row { margin-bottom: 16px; }</style>
        <div class="form-row">
          <dees-input-text .label=${'Name'} .key=${'name'} .description=${'A human-readable name for this sync configuration'}></dees-input-text>
        </div>
        <div class="form-row">
          <dees-input-dropdown
            .label=${'Source Connection'}
            .key=${'sourceConnectionId'}
            .description=${'The connection to read repositories from (filtered by its group filter)'}
            .options=${connectionOptions}
            .selectedOption=${connectionOptions[0]}
          ></dees-input-dropdown>
        </div>
        <div class="form-row">
          <dees-input-dropdown
            .label=${'Target Connection'}
            .key=${'targetConnectionId'}
            .description=${'The connection to push repositories to'}
            .options=${connectionOptions}
            .selectedOption=${connectionOptions[1] || connectionOptions[0]}
          ></dees-input-dropdown>
        </div>
        <div class="form-row">
          <dees-input-text .label=${'Target Group Offset'} .key=${'targetGroupOffset'} .description=${'Path prefix for target repos (e.g. "mirror/gitlab"). Leave empty for no prefix — repos land at their relative path.'}></dees-input-text>
        </div>
        <div class="form-row">
          <dees-input-text .label=${'Interval (minutes)'} .key=${'intervalMinutes'} .value=${'5'} .description=${'How often to run this sync automatically'}></dees-input-text>
        </div>
        <div class="form-row">
          <dees-input-checkbox .label=${'Enforce Deletion'} .key=${'enforceDelete'} .value=${false} .description=${'When enabled, repos on the target not present on the source will be moved to an obsolete group (private).'}></dees-input-checkbox>
        </div>
        <div class="form-row">
          <dees-input-checkbox .label=${'Enforce Group Deletion'} .key=${'enforceGroupDelete'} .value=${false} .description=${'When enabled, groups/orgs on the target not present on the source will be moved to obsolete.'}></dees-input-checkbox>
        </div>
        <div class="form-row">
          <dees-input-checkbox .label=${'Add Mirror Hint'} .key=${'addMirrorHint'} .value=${false} .description=${'When enabled, target descriptions get "(This is a mirror of ...)" appended.'}></dees-input-checkbox>
        </div>
        <div class="form-row">
          <dees-input-checkbox .label=${'Group Avatars for Projects'} .key=${'useGroupAvatarsForProjects'} .value=${false} .description=${'When enabled, projects without their own avatar inherit the group avatar.'}></dees-input-checkbox>
        </div>
      `,
      menuOptions: [
        { name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
        {
          name: 'Create',
          action: async (modal: any) => {
            const inputs = modal.shadowRoot.querySelectorAll('dees-input-text, dees-input-dropdown, dees-input-checkbox');
            const data: any = {};
            for (const input of inputs) {
              if (input.key === 'sourceConnectionId' || input.key === 'targetConnectionId') {
                data[input.key] = input.selectedOption?.key || '';
              } else if (input.key === 'enforceDelete' || input.key === 'enforceGroupDelete' || input.key === 'addMirrorHint' || input.key === 'useGroupAvatarsForProjects') {
                data[input.key] = input.getValue();
              } else {
                data[input.key] = input.value || '';
              }
            }
            await appstate.syncStatePart.dispatchAction(appstate.createSyncConfigAction, {
              name: data.name,
              sourceConnectionId: data.sourceConnectionId,
              targetConnectionId: data.targetConnectionId,
              targetGroupOffset: data.targetGroupOffset || undefined,
              intervalMinutes: parseInt(data.intervalMinutes) || 5,
              enforceDelete: !!data.enforceDelete,
              enforceGroupDelete: !!data.enforceGroupDelete,
              addMirrorHint: !!data.addMirrorHint,
              useGroupAvatarsForProjects: !!data.useGroupAvatarsForProjects,
            });
            modal.destroy();
          },
        },
      ],
    });
  }

  private async editSyncConfig(item: any) {
    await plugins.deesCatalog.DeesModal.createAndShow({
      heading: `Edit Sync: ${item.name}`,
      content: html`
        <style>.form-row { margin-bottom: 16px; }</style>
        <div class="form-row">
          <dees-input-text .label=${'Name'} .key=${'name'} .value=${item.name} .description=${'A human-readable name for this sync configuration'}></dees-input-text>
        </div>
        <div class="form-row">
          <dees-input-text .label=${'Target Group Offset'} .key=${'targetGroupOffset'} .value=${item.targetGroupOffset || ''} .description=${'Path prefix for target repos (e.g. "mirror/gitlab"). Leave empty for no prefix — repos land at their relative path.'}></dees-input-text>
        </div>
        <div class="form-row">
          <dees-input-text .label=${'Interval (minutes)'} .key=${'intervalMinutes'} .value=${String(item.intervalMinutes)} .description=${'How often to run this sync automatically'}></dees-input-text>
        </div>
        <div class="form-row">
          <dees-input-checkbox .label=${'Enforce Deletion'} .key=${'enforceDelete'} .value=${!!item.enforceDelete} .description=${'When enabled, repos on the target not present on the source will be moved to an obsolete group (private).'}></dees-input-checkbox>
        </div>
        <div class="form-row">
          <dees-input-checkbox .label=${'Enforce Group Deletion'} .key=${'enforceGroupDelete'} .value=${!!item.enforceGroupDelete} .description=${'When enabled, groups/orgs on the target not present on the source will be moved to obsolete.'}></dees-input-checkbox>
        </div>
        <div class="form-row">
          <dees-input-checkbox .label=${'Add Mirror Hint'} .key=${'addMirrorHint'} .value=${!!item.addMirrorHint} .description=${'When enabled, target descriptions get "(This is a mirror of ...)" appended.'}></dees-input-checkbox>
        </div>
        <div class="form-row">
          <dees-input-checkbox .label=${'Group Avatars for Projects'} .key=${'useGroupAvatarsForProjects'} .value=${!!item.useGroupAvatarsForProjects} .description=${'When enabled, projects without their own avatar inherit the group avatar.'}></dees-input-checkbox>
        </div>
      `,
      menuOptions: [
        { name: 'Cancel', action: async (modal: any) => { modal.destroy(); } },
        {
          name: 'Save',
          action: async (modal: any) => {
            const inputs = modal.shadowRoot.querySelectorAll('dees-input-text, dees-input-checkbox');
            const data: any = {};
            for (const input of inputs) {
              if (input.key === 'enforceDelete' || input.key === 'enforceGroupDelete' || input.key === 'addMirrorHint' || input.key === 'useGroupAvatarsForProjects') {
                data[input.key] = input.getValue();
              } else {
                data[input.key] = input.value || '';
              }
            }
            await appstate.syncStatePart.dispatchAction(appstate.updateSyncConfigAction, {
              syncConfigId: item.id,
              name: data.name,
              targetGroupOffset: data.targetGroupOffset || undefined,
              intervalMinutes: parseInt(data.intervalMinutes) || 5,
              enforceDelete: !!data.enforceDelete,
              enforceGroupDelete: !!data.enforceGroupDelete,
              addMirrorHint: !!data.addMirrorHint,
              useGroupAvatarsForProjects: !!data.useGroupAvatarsForProjects,
            });
            modal.destroy();
          },
        },
      ],
    });
  }

  private async previewSync(item: any) {
    try {
      const { mappings, deletions, groupDeletions } = await appstate.previewSync(item.id);

      // Compute the full obsolete group path for display
      const targetConn = this.connectionsState.connections.find((c: any) => c.id === item.targetConnectionId);
      let obsoletePath: string;
      if (targetConn?.providerType === 'gitea') {
        const segments = item.targetGroupOffset ? item.targetGroupOffset.split('/') : [];
        const orgName = segments[0] || targetConn?.groupFilter || 'default';
        obsoletePath = `${orgName}-obsolete`;
      } else {
        obsoletePath = item.targetGroupOffset ? `${item.targetGroupOffset}/obsolete` : 'obsolete';
      }

      await plugins.deesCatalog.DeesModal.createAndShow({
        heading: `Preview Sync: "${item.name}"`,
        content: html`
          <style>
            .preview-list { color: #fff; max-height: 400px; overflow-y: auto; }
            .preview-item { display: flex; align-items: center; gap: 12px; padding: 6px 0; border-bottom: 1px solid #333; font-size: 13px; }
            .preview-source { color: #aaa; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            .preview-arrow { color: #666; flex-shrink: 0; }
            .preview-target { color: #00ff88; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            .preview-count { color: #888; font-size: 12px; margin-bottom: 12px; }
            .preview-delete { color: #ff4444; padding: 6px 0; border-bottom: 1px solid #333; font-size: 13px; display: flex; align-items: center; gap: 8px; }
            .preview-delete-marker { flex-shrink: 0; }
            .preview-delete-path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            .preview-section { margin-top: 16px; }
            .preview-section-header { color: #ff4444; font-size: 12px; font-weight: 600; margin-bottom: 8px; }
          </style>
          <div class="preview-count">${mappings.length} repositories will be synced</div>
          <div class="preview-list">
            ${mappings.map((m: any) => html`
              <div class="preview-item">
                <span class="preview-source">${m.sourceFullPath}</span>
                <span class="preview-arrow">&rarr;</span>
                <span class="preview-target">${m.targetFullPath}</span>
              </div>
            `)}
            ${mappings.length === 0 ? html`<p style="color: #888;">No repositories found on source.</p>` : ''}
          </div>
          ${deletions.length > 0 ? html`
            <div class="preview-section">
              <div class="preview-section-header">${deletions.length} target repositor${deletions.length === 1 ? 'y' : 'ies'} will be moved to ${obsoletePath}</div>
              <div class="preview-list">
                ${deletions.map((d: string) => html`
                  <div class="preview-delete">
                    <span class="preview-delete-marker">→</span>
                    <span class="preview-delete-path">${d}</span>
                  </div>
                `)}
              </div>
            </div>
          ` : ''}
          ${groupDeletions.length > 0 ? html`
            <div class="preview-section">
              <div class="preview-section-header">${groupDeletions.length} target group${groupDeletions.length === 1 ? '' : 's'} will be moved to ${obsoletePath}</div>
              <div class="preview-list">
                ${groupDeletions.map((g: string) => html`
                  <div class="preview-delete">
                    <span class="preview-delete-marker">→</span>
                    <span class="preview-delete-path">${g}</span>
                  </div>
                `)}
              </div>
            </div>
          ` : ''}
        `,
        menuOptions: [
          { name: 'Close', action: async (modal: any) => { modal.destroy(); } },
        ],
      });
    } catch (err: any) {
      await plugins.deesCatalog.DeesModal.createAndShow({
        heading: 'Preview Failed',
        content: html`<p style="color: #ff4444;">${err.message || String(err)}</p>`,
        menuOptions: [
          { name: 'Close', action: async (modal: any) => { modal.destroy(); } },
        ],
      });
    }
  }

  private async viewRepoStatuses(item: any) {
    await appstate.syncStatePart.dispatchAction(appstate.fetchSyncRepoStatusesAction, {
      syncConfigId: item.id,
    });

    const statuses = appstate.syncStatePart.getState().repoStatuses;
    await plugins.deesCatalog.DeesModal.createAndShow({
      heading: `Sync "${item.name}" - Repo Statuses`,
      content: html`
        <style>
          .repo-list { color: #fff; max-height: 400px; overflow-y: auto; }
          .repo-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #333; }
          .repo-path { font-weight: 600; font-size: 13px; }
          .repo-status { font-size: 12px; text-transform: uppercase; }
          .repo-status.synced { color: #00ff88; }
          .repo-status.error { color: #ff4444; }
          .repo-status.pending { color: #ffaa00; }
          .repo-error { font-size: 11px; color: #ff6666; margin-top: 4px; }
        </style>
        <div class="repo-list">
          ${statuses.map((s: any) => html`
            <div class="repo-item">
              <div>
                <div class="repo-path">${s.sourceFullPath}</div>
                ${s.lastSyncError ? html`<div class="repo-error">${s.lastSyncError}</div>` : ''}
              </div>
              <div>
                <span class="repo-status ${s.status}">${s.status}</span>
                <div style="font-size: 11px; color: #888;">${s.lastSyncAt ? new Date(s.lastSyncAt).toLocaleString() : ''}</div>
              </div>
            </div>
          `)}
          ${statuses.length === 0 ? html`<p style="color: #888;">No repos synced yet.</p>` : ''}
        </div>
      `,
      menuOptions: [
        { name: 'Close', action: async (modal: any) => { modal.destroy(); } },
      ],
    });
  }
}
