/**
 * Synor Governance SDK Client
 * Proposals, voting, DAOs, and vesting.
 */

import {
  GovernanceConfig,
  ProposalDraft,
  Proposal,
  ProposalFilter,
  Vote,
  VoteReceipt,
  DelegationReceipt,
  VotingPower,
  DaoConfig,
  Dao,
  VestingSchedule,
  VestingContract,
  ClaimResult,
  GovernanceTransaction,
  ProposalsResponse,
  DaosResponse,
  VestingContractsResponse,
} from './types';

const DEFAULT_ENDPOINT = 'https://governance.synor.io/v1';
const DEFAULT_TIMEOUT = 30000;
const DEFAULT_RETRIES = 3;

export class SynorGovernance {
  private config: Required<GovernanceConfig>;
  private closed = false;

  constructor(config: GovernanceConfig) {
    this.config = {
      apiKey: config.apiKey,
      endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
      timeout: config.timeout ?? DEFAULT_TIMEOUT,
      retries: config.retries ?? DEFAULT_RETRIES,
      debug: config.debug ?? false,
    };
  }

  // ==================== Proposal Operations ====================

  /**
   * Create a new proposal.
   */
  async createProposal(draft: ProposalDraft): Promise<Proposal> {
    return await this.post<Proposal>('/proposals', draft);
  }

  /**
   * Get a proposal by ID.
   */
  async getProposal(proposalId: string): Promise<Proposal> {
    return await this.get<Proposal>(`/proposals/${encodeURIComponent(proposalId)}`);
  }

  /**
   * List proposals with optional filtering.
   */
  async listProposals(filter?: ProposalFilter): Promise<Proposal[]> {
    const params = new URLSearchParams();
    if (filter?.status) params.set('status', filter.status);
    if (filter?.type) params.set('type', filter.type);
    if (filter?.proposer) params.set('proposer', filter.proposer);
    if (filter?.limit) params.set('limit', filter.limit.toString());
    if (filter?.offset) params.set('offset', filter.offset.toString());
    const query = params.toString() ? `?${params.toString()}` : '';
    const response = await this.get<ProposalsResponse>(`/proposals${query}`);
    return response.proposals ?? [];
  }

  /**
   * Cancel a proposal.
   */
  async cancelProposal(proposalId: string): Promise<Proposal> {
    return await this.post<Proposal>(`/proposals/${encodeURIComponent(proposalId)}/cancel`, {});
  }

  /**
   * Execute a passed proposal.
   */
  async executeProposal(proposalId: string): Promise<GovernanceTransaction> {
    return await this.post<GovernanceTransaction>(
      `/proposals/${encodeURIComponent(proposalId)}/execute`,
      {}
    );
  }

  // ==================== Voting Operations ====================

  /**
   * Vote on a proposal.
   */
  async vote(proposalId: string, vote: Vote, weight?: string): Promise<VoteReceipt> {
    const body: Record<string, unknown> = { ...vote };
    if (weight) body.weight = weight;
    return await this.post<VoteReceipt>(
      `/proposals/${encodeURIComponent(proposalId)}/vote`,
      body
    );
  }

  /**
   * Get votes for a proposal.
   */
  async getVotes(
    proposalId: string,
    limit?: number,
    offset?: number
  ): Promise<VoteReceipt[]> {
    const params = new URLSearchParams();
    if (limit) params.set('limit', limit.toString());
    if (offset) params.set('offset', offset.toString());
    const query = params.toString() ? `?${params.toString()}` : '';
    const response = await this.get<{ votes: VoteReceipt[] }>(
      `/proposals/${encodeURIComponent(proposalId)}/votes${query}`
    );
    return response.votes ?? [];
  }

  /**
   * Delegate voting power to another address.
   */
  async delegate(to: string, amount?: string): Promise<DelegationReceipt> {
    const body: Record<string, unknown> = { to };
    if (amount) body.amount = amount;
    return await this.post<DelegationReceipt>('/voting/delegate', body);
  }

  /**
   * Undelegate voting power.
   */
  async undelegate(from?: string): Promise<DelegationReceipt> {
    const body: Record<string, unknown> = {};
    if (from) body.from = from;
    return await this.post<DelegationReceipt>('/voting/undelegate', body);
  }

  /**
   * Get voting power for an address.
   */
  async getVotingPower(address: string): Promise<VotingPower> {
    return await this.get<VotingPower>(`/voting/power/${encodeURIComponent(address)}`);
  }

  /**
   * Get current voting power for the authenticated user.
   */
  async getMyVotingPower(): Promise<VotingPower> {
    return await this.get<VotingPower>('/voting/power');
  }

  // ==================== DAO Operations ====================

  /**
   * Create a new DAO.
   */
  async createDao(config: DaoConfig): Promise<Dao> {
    return await this.post<Dao>('/daos', config);
  }

  /**
   * Get a DAO by ID.
   */
  async getDao(daoId: string): Promise<Dao> {
    return await this.get<Dao>(`/daos/${encodeURIComponent(daoId)}`);
  }

  /**
   * List DAOs.
   */
  async listDaos(limit?: number, offset?: number): Promise<Dao[]> {
    const params = new URLSearchParams();
    if (limit) params.set('limit', limit.toString());
    if (offset) params.set('offset', offset.toString());
    const query = params.toString() ? `?${params.toString()}` : '';
    const response = await this.get<DaosResponse>(`/daos${query}`);
    return response.daos ?? [];
  }

  /**
   * Get DAO members.
   */
  async getDaoMembers(
    daoId: string,
    limit?: number,
    offset?: number
  ): Promise<{ address: string; power: string }[]> {
    const params = new URLSearchParams();
    if (limit) params.set('limit', limit.toString());
    if (offset) params.set('offset', offset.toString());
    const query = params.toString() ? `?${params.toString()}` : '';
    const response = await this.get<{ members: { address: string; power: string }[] }>(
      `/daos/${encodeURIComponent(daoId)}/members${query}`
    );
    return response.members ?? [];
  }

  // ==================== Vesting Operations ====================

  /**
   * Create a vesting schedule.
   */
  async createVestingSchedule(schedule: VestingSchedule): Promise<VestingContract> {
    return await this.post<VestingContract>('/vesting', schedule);
  }

  /**
   * Get a vesting contract.
   */
  async getVestingContract(contractId: string): Promise<VestingContract> {
    return await this.get<VestingContract>(`/vesting/${encodeURIComponent(contractId)}`);
  }

  /**
   * List vesting contracts.
   */
  async listVestingContracts(beneficiary?: string): Promise<VestingContract[]> {
    const query = beneficiary ? `?beneficiary=${encodeURIComponent(beneficiary)}` : '';
    const response = await this.get<VestingContractsResponse>(`/vesting${query}`);
    return response.contracts ?? [];
  }

  /**
   * Claim vested tokens.
   */
  async claimVested(contractId: string): Promise<ClaimResult> {
    return await this.post<ClaimResult>(
      `/vesting/${encodeURIComponent(contractId)}/claim`,
      {}
    );
  }

  /**
   * Revoke a vesting contract (if revocable).
   */
  async revokeVesting(contractId: string): Promise<VestingContract> {
    return await this.post<VestingContract>(
      `/vesting/${encodeURIComponent(contractId)}/revoke`,
      {}
    );
  }

  /**
   * Get claimable amount for a vesting contract.
   */
  async getClaimableAmount(contractId: string): Promise<{ claimable: string; vested: string }> {
    return await this.get<{ claimable: string; vested: string }>(
      `/vesting/${encodeURIComponent(contractId)}/claimable`
    );
  }

  // ==================== Lifecycle ====================

  /**
   * Health check.
   */
  async healthCheck(): Promise<boolean> {
    try {
      const response = await this.get<{ status: string }>('/health');
      return response.status === 'healthy';
    } catch {
      return false;
    }
  }

  /**
   * Check if client is closed.
   */
  get isClosed(): boolean {
    return this.closed;
  }

  /**
   * Close the client.
   */
  close(): void {
    this.closed = true;
  }

  // ==================== Private HTTP Methods ====================

  private async get<T>(path: string): Promise<T> {
    return await this.execute(async () => {
      const response = await fetch(`${this.config.endpoint}${path}`, {
        method: 'GET',
        headers: this.headers(),
        signal: AbortSignal.timeout(this.config.timeout),
      });
      await this.ensureSuccess(response);
      return await response.json();
    });
  }

  private async post<T>(path: string, body: unknown): Promise<T> {
    return await this.execute(async () => {
      const response = await fetch(`${this.config.endpoint}${path}`, {
        method: 'POST',
        headers: this.headers(),
        body: JSON.stringify(body),
        signal: AbortSignal.timeout(this.config.timeout),
      });
      await this.ensureSuccess(response);
      return await response.json();
    });
  }

  private headers(): Record<string, string> {
    return {
      'Authorization': `Bearer ${this.config.apiKey}`,
      'Content-Type': 'application/json',
      'X-SDK-Version': 'js/0.1.0',
    };
  }

  private async execute<T>(operation: () => Promise<T>): Promise<T> {
    if (this.closed) {
      throw new GovernanceError('Client has been closed');
    }

    let lastError: Error | undefined;
    for (let attempt = 0; attempt < this.config.retries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error as Error;
        if (this.config.debug) {
          console.log(`Attempt ${attempt + 1} failed:`, error);
        }
        if (attempt < this.config.retries - 1) {
          await this.sleep(1000 * Math.pow(2, attempt));
        }
      }
    }
    throw lastError!;
  }

  private async ensureSuccess(response: Response): Promise<void> {
    if (!response.ok) {
      const body = await response.text();
      let message = `HTTP ${response.status}`;
      let code: string | undefined;
      try {
        const error = JSON.parse(body);
        message = error.message ?? message;
        code = error.code;
      } catch {
        // ignore parse errors
      }
      throw new GovernanceError(message, code, response.status);
    }
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

/**
 * Governance SDK Error
 */
export class GovernanceError extends Error {
  constructor(
    message: string,
    public readonly code?: string,
    public readonly statusCode?: number
  ) {
    super(message);
    this.name = 'GovernanceError';
  }
}

// Re-export types
export * from './types';
