package io.synor.governance;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Synor Governance SDK for Java.
 * Proposals, voting, DAOs, and vesting operations.
 */
public class SynorGovernance implements AutoCloseable {
    private static final String DEFAULT_ENDPOINT = "https://governance.synor.io/v1";
    private static final Gson gson = new GsonBuilder().create();
    private static final Set<ProposalStatus> FINAL_STATUSES = Set.of(
            ProposalStatus.passed, ProposalStatus.rejected, ProposalStatus.executed, ProposalStatus.cancelled
    );

    private final GovernanceConfig config;
    private final HttpClient httpClient;
    private final AtomicBoolean closed = new AtomicBoolean(false);

    public SynorGovernance(String apiKey) {
        this(new GovernanceConfig(apiKey, DEFAULT_ENDPOINT, 60, 3, false));
    }

    public SynorGovernance(GovernanceConfig config) {
        this.config = config;
        this.httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(config.getTimeoutSecs()))
                .build();
    }

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

    public CompletableFuture<Proposal> createProposal(ProposalDraft proposal) {
        Map<String, Object> body = new HashMap<>();
        body.put("title", proposal.getTitle());
        body.put("description", proposal.getDescription());
        if (proposal.getDiscussionUrl() != null) body.put("discussionUrl", proposal.getDiscussionUrl());
        if (proposal.getVotingStartTime() != null) body.put("votingStartTime", proposal.getVotingStartTime());
        if (proposal.getVotingEndTime() != null) body.put("votingEndTime", proposal.getVotingEndTime());
        if (proposal.getActions() != null) body.put("actions", proposal.getActions());
        if (proposal.getDaoId() != null) body.put("daoId", proposal.getDaoId());
        return request("POST", "/proposals", body)
                .thenApply(resp -> gson.fromJson(resp, Proposal.class));
    }

    public CompletableFuture<Proposal> getProposal(String proposalId) {
        return request("GET", "/proposals/" + encode(proposalId), null)
                .thenApply(resp -> gson.fromJson(resp, Proposal.class));
    }

    public CompletableFuture<List<Proposal>> listProposals(ProposalFilter filter) {
        StringBuilder path = new StringBuilder("/proposals");
        if (filter != null) {
            StringBuilder params = new StringBuilder();
            if (filter.getStatus() != null) params.append("status=").append(filter.getStatus().name().toLowerCase());
            if (filter.getProposer() != null) {
                if (params.length() > 0) params.append("&");
                params.append("proposer=").append(encode(filter.getProposer()));
            }
            if (filter.getDaoId() != null) {
                if (params.length() > 0) params.append("&");
                params.append("daoId=").append(encode(filter.getDaoId()));
            }
            if (filter.getLimit() != null) {
                if (params.length() > 0) params.append("&");
                params.append("limit=").append(filter.getLimit());
            }
            if (filter.getOffset() != null) {
                if (params.length() > 0) params.append("&");
                params.append("offset=").append(filter.getOffset());
            }
            if (params.length() > 0) path.append("?").append(params);
        }
        return request("GET", path.toString(), null)
                .thenApply(resp -> {
                    Type type = new TypeToken<ProposalsResponse>(){}.getType();
                    ProposalsResponse result = gson.fromJson(resp, type);
                    return result.proposals;
                });
    }

    public CompletableFuture<Proposal> cancelProposal(String proposalId) {
        return request("POST", "/proposals/" + encode(proposalId) + "/cancel", null)
                .thenApply(resp -> gson.fromJson(resp, Proposal.class));
    }

    public CompletableFuture<Proposal> executeProposal(String proposalId) {
        return request("POST", "/proposals/" + encode(proposalId) + "/execute", null)
                .thenApply(resp -> gson.fromJson(resp, Proposal.class));
    }

    public CompletableFuture<Proposal> waitForProposal(String proposalId, long pollIntervalMs, long maxWaitMs) {
        long deadline = System.currentTimeMillis() + maxWaitMs;
        return waitForProposalInternal(proposalId, pollIntervalMs, deadline);
    }

    private CompletableFuture<Proposal> waitForProposalInternal(String id, long pollMs, long deadline) {
        if (System.currentTimeMillis() >= deadline) {
            return CompletableFuture.failedFuture(new GovernanceException("Timeout waiting for proposal completion"));
        }
        return getProposal(id).thenCompose(proposal -> {
            if (FINAL_STATUSES.contains(proposal.getStatus())) {
                return CompletableFuture.completedFuture(proposal);
            }
            return CompletableFuture.runAsync(() -> sleep(pollMs))
                    .thenCompose(v -> waitForProposalInternal(id, pollMs, deadline));
        });
    }

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

    public CompletableFuture<VoteReceipt> vote(String proposalId, Vote vote, String weight) {
        Map<String, Object> body = new HashMap<>();
        body.put("choice", vote.getChoice().name().toLowerCase());
        if (vote.getReason() != null) body.put("reason", vote.getReason());
        if (weight != null) body.put("weight", weight);
        return request("POST", "/proposals/" + encode(proposalId) + "/vote", body)
                .thenApply(resp -> gson.fromJson(resp, VoteReceipt.class));
    }

    public CompletableFuture<List<VoteReceipt>> getVotes(String proposalId) {
        return request("GET", "/proposals/" + encode(proposalId) + "/votes", null)
                .thenApply(resp -> {
                    Type type = new TypeToken<VotesResponse>(){}.getType();
                    VotesResponse result = gson.fromJson(resp, type);
                    return result.votes;
                });
    }

    public CompletableFuture<VoteReceipt> getMyVote(String proposalId) {
        return request("GET", "/proposals/" + encode(proposalId) + "/votes/me", null)
                .thenApply(resp -> gson.fromJson(resp, VoteReceipt.class));
    }

    public CompletableFuture<DelegationReceipt> delegate(String delegatee, String amount) {
        Map<String, Object> body = new HashMap<>();
        body.put("delegatee", delegatee);
        if (amount != null) body.put("amount", amount);
        return request("POST", "/voting/delegate", body)
                .thenApply(resp -> gson.fromJson(resp, DelegationReceipt.class));
    }

    public CompletableFuture<DelegationReceipt> undelegate(String delegatee) {
        return request("POST", "/voting/undelegate", Map.of("delegatee", delegatee))
                .thenApply(resp -> gson.fromJson(resp, DelegationReceipt.class));
    }

    public CompletableFuture<VotingPower> getVotingPower(String address) {
        return request("GET", "/voting/power/" + encode(address), null)
                .thenApply(resp -> gson.fromJson(resp, VotingPower.class));
    }

    public CompletableFuture<List<DelegationReceipt>> getDelegations(String address) {
        return request("GET", "/voting/delegations/" + encode(address), null)
                .thenApply(resp -> {
                    Type type = new TypeToken<DelegationsResponse>(){}.getType();
                    DelegationsResponse result = gson.fromJson(resp, type);
                    return result.delegations;
                });
    }

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

    public CompletableFuture<Dao> createDao(DaoConfig daoConfig) {
        Map<String, Object> body = new HashMap<>();
        body.put("name", daoConfig.getName());
        body.put("description", daoConfig.getDescription());
        body.put("type", daoConfig.getType().name().toLowerCase());
        if (daoConfig.getTokenAddress() != null) body.put("tokenAddress", daoConfig.getTokenAddress());
        if (daoConfig.getQuorumPercent() != null) body.put("quorumPercent", daoConfig.getQuorumPercent());
        body.put("votingPeriodDays", daoConfig.getVotingPeriodDays());
        body.put("timelockDays", daoConfig.getTimelockDays());
        if (daoConfig.getProposalThreshold() != null) body.put("proposalThreshold", daoConfig.getProposalThreshold());
        if (daoConfig.getMultisigMembers() != null) body.put("multisigMembers", daoConfig.getMultisigMembers());
        if (daoConfig.getMultisigThreshold() != null) body.put("multisigThreshold", daoConfig.getMultisigThreshold());
        return request("POST", "/daos", body)
                .thenApply(resp -> gson.fromJson(resp, Dao.class));
    }

    public CompletableFuture<Dao> getDao(String daoId) {
        return request("GET", "/daos/" + encode(daoId), null)
                .thenApply(resp -> gson.fromJson(resp, Dao.class));
    }

    public CompletableFuture<List<Dao>> listDaos(Integer limit, Integer offset) {
        StringBuilder path = new StringBuilder("/daos");
        if (limit != null || offset != null) {
            path.append("?");
            if (limit != null) path.append("limit=").append(limit);
            if (offset != null) {
                if (limit != null) path.append("&");
                path.append("offset=").append(offset);
            }
        }
        return request("GET", path.toString(), null)
                .thenApply(resp -> {
                    Type type = new TypeToken<DaosResponse>(){}.getType();
                    DaosResponse result = gson.fromJson(resp, type);
                    return result.daos;
                });
    }

    public CompletableFuture<DaoTreasury> getDaoTreasury(String daoId) {
        return request("GET", "/daos/" + encode(daoId) + "/treasury", null)
                .thenApply(resp -> gson.fromJson(resp, DaoTreasury.class));
    }

    public CompletableFuture<List<String>> getDaoMembers(String daoId) {
        return request("GET", "/daos/" + encode(daoId) + "/members", null)
                .thenApply(resp -> {
                    Type type = new TypeToken<MembersResponse>(){}.getType();
                    MembersResponse result = gson.fromJson(resp, type);
                    return result.members;
                });
    }

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

    public CompletableFuture<VestingContract> createVestingSchedule(VestingSchedule schedule) {
        Map<String, Object> body = new HashMap<>();
        body.put("beneficiary", schedule.getBeneficiary());
        body.put("totalAmount", schedule.getTotalAmount());
        body.put("startTime", schedule.getStartTime());
        body.put("cliffDuration", schedule.getCliffDuration());
        body.put("vestingDuration", schedule.getVestingDuration());
        body.put("revocable", schedule.isRevocable());
        return request("POST", "/vesting", body)
                .thenApply(resp -> gson.fromJson(resp, VestingContract.class));
    }

    public CompletableFuture<VestingContract> getVestingContract(String contractId) {
        return request("GET", "/vesting/" + encode(contractId), null)
                .thenApply(resp -> gson.fromJson(resp, VestingContract.class));
    }

    public CompletableFuture<List<VestingContract>> listVestingContracts(String beneficiary) {
        StringBuilder path = new StringBuilder("/vesting");
        if (beneficiary != null) {
            path.append("?beneficiary=").append(encode(beneficiary));
        }
        return request("GET", path.toString(), null)
                .thenApply(resp -> {
                    Type type = new TypeToken<VestingContractsResponse>(){}.getType();
                    VestingContractsResponse result = gson.fromJson(resp, type);
                    return result.contracts;
                });
    }

    public CompletableFuture<ClaimReceipt> claimVested(String contractId) {
        return request("POST", "/vesting/" + encode(contractId) + "/claim", null)
                .thenApply(resp -> gson.fromJson(resp, ClaimReceipt.class));
    }

    public CompletableFuture<VestingContract> revokeVesting(String contractId) {
        return request("POST", "/vesting/" + encode(contractId) + "/revoke", null)
                .thenApply(resp -> gson.fromJson(resp, VestingContract.class));
    }

    public CompletableFuture<String> getReleasableAmount(String contractId) {
        return request("GET", "/vesting/" + encode(contractId) + "/releasable", null)
                .thenApply(resp -> {
                    Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
                    return (String) map.get("amount");
                });
    }

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

    @Override
    public void close() {
        closed.set(true);
    }

    public boolean isClosed() {
        return closed.get();
    }

    public CompletableFuture<Boolean> healthCheck() {
        return request("GET", "/health", null)
                .thenApply(resp -> {
                    Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
                    return "healthy".equals(map.get("status"));
                })
                .exceptionally(e -> false);
    }

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

    private CompletableFuture<String> request(String method, String path, Object body) {
        if (closed.get()) {
            return CompletableFuture.failedFuture(new GovernanceException("Client has been closed"));
        }
        return doRequest(method, path, body, 0);
    }

    private CompletableFuture<String> doRequest(String method, String path, Object body, int attempt) {
        String url = config.getEndpoint() + path;

        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .header("Authorization", "Bearer " + config.getApiKey())
                .header("Content-Type", "application/json")
                .header("X-SDK-Version", "java/0.1.0")
                .timeout(Duration.ofSeconds(config.getTimeoutSecs()));

        HttpRequest.BodyPublisher bodyPub = body != null
                ? HttpRequest.BodyPublishers.ofString(gson.toJson(body))
                : HttpRequest.BodyPublishers.noBody();

        switch (method) {
            case "GET": builder.GET(); break;
            case "POST": builder.POST(bodyPub); break;
            case "PUT": builder.PUT(bodyPub); break;
            case "DELETE": builder.method("DELETE", bodyPub); break;
        }

        return httpClient.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofString())
                .thenCompose(response -> {
                    if (response.statusCode() >= 400) {
                        Map<String, Object> errorBody = gson.fromJson(response.body(),
                                new TypeToken<Map<String, Object>>(){}.getType());
                        String message = errorBody != null && errorBody.get("message") != null
                                ? (String) errorBody.get("message") : "HTTP " + response.statusCode();
                        String code = errorBody != null ? (String) errorBody.get("code") : null;
                        return CompletableFuture.failedFuture(
                                new GovernanceException(message, code, response.statusCode()));
                    }
                    return CompletableFuture.completedFuture(response.body());
                })
                .exceptionally(e -> {
                    if (attempt < config.getRetries() - 1) {
                        sleep((long) Math.pow(2, attempt) * 1000);
                        return doRequest(method, path, body, attempt + 1).join();
                    }
                    throw new RuntimeException(e);
                });
    }

    private static String encode(String value) {
        return java.net.URLEncoder.encode(value, java.nio.charset.StandardCharsets.UTF_8);
    }

    private static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }

    // Helper response types
    private static class ProposalsResponse { List<Proposal> proposals; }
    private static class VotesResponse { List<VoteReceipt> votes; }
    private static class DelegationsResponse { List<DelegationReceipt> delegations; }
    private static class DaosResponse { List<Dao> daos; }
    private static class MembersResponse { List<String> members; }
    private static class VestingContractsResponse { List<VestingContract> contracts; }
}
