package net.siegeln.cameleer.saas.portal;

import net.siegeln.cameleer.saas.config.TenantContext;
import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
import net.siegeln.cameleer.saas.identity.ServerApiClient;
import net.siegeln.cameleer.saas.license.LicenseEntity;
import net.siegeln.cameleer.saas.license.LicenseService;
import net.siegeln.cameleer.saas.provisioning.ProvisioningProperties;
import net.siegeln.cameleer.saas.provisioning.TenantProvisioner;
import net.siegeln.cameleer.saas.tenant.TenantEntity;
import net.siegeln.cameleer.saas.tenant.TenantService;
import net.siegeln.cameleer.saas.vendor.VendorTenantService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@Service
public class TenantPortalService {

    private static final Logger log = LoggerFactory.getLogger(TenantPortalService.class);

    private final TenantService tenantService;
    private final LicenseService licenseService;
    private final ServerApiClient serverApiClient;
    private final LogtoManagementClient logtoClient;
    private final TenantProvisioner tenantProvisioner;
    private final ProvisioningProperties provisioningProps;
    private final VendorTenantService vendorTenantService;

    public TenantPortalService(TenantService tenantService,
                               LicenseService licenseService,
                               ServerApiClient serverApiClient,
                               LogtoManagementClient logtoClient,
                               TenantProvisioner tenantProvisioner,
                               ProvisioningProperties provisioningProps,
                               @Lazy VendorTenantService vendorTenantService) {
        this.tenantService = tenantService;
        this.licenseService = licenseService;
        this.serverApiClient = serverApiClient;
        this.logtoClient = logtoClient;
        this.tenantProvisioner = tenantProvisioner;
        this.provisioningProps = provisioningProps;
        this.vendorTenantService = vendorTenantService;
    }

    // --- Inner records ---

    public record DashboardData(
            String name, String slug, String tier, String status,
            boolean serverHealthy, String serverStatus, String serverEndpoint,
            String licenseTier, long licenseDaysRemaining,
            Map<String, Object> limits, Map<String, Object> features,
            int agentCount, int environmentCount
    ) {}

    public record LicenseData(
            UUID id, String tier, Map<String, Object> features, Map<String, Object> limits,
            Instant issuedAt, Instant expiresAt, String token, long daysRemaining
    ) {}

    public record TenantSettingsData(
            String name, String slug, String tier, String status,
            String serverEndpoint, Instant createdAt
    ) {}

    // --- Helpers ---

    private TenantEntity resolveTenant() {
        UUID tenantId = TenantContext.getTenantId();
        return tenantService.getById(tenantId)
                .orElseThrow(() -> new IllegalStateException("Tenant not found: " + tenantId));
    }

    private long daysUntil(Instant instant) {
        if (instant == null) return 0;
        long days = ChronoUnit.DAYS.between(Instant.now(), instant);
        return Math.max(0, days);
    }

    // --- Service methods ---

    public DashboardData getDashboard() {
        TenantEntity tenant = resolveTenant();
        String endpoint = tenant.getServerEndpoint();

        boolean serverHealthy = false;
        String serverStatus = "NO_ENDPOINT";
        int agentCount = 0;
        int environmentCount = 0;
        if (endpoint != null && !endpoint.isBlank()) {
            var health = serverApiClient.getHealth(endpoint);
            serverHealthy = health.healthy();
            serverStatus = health.status();
            if (serverHealthy) {
                agentCount = serverApiClient.getAgentCount(endpoint);
                environmentCount = serverApiClient.getEnvironmentCount(endpoint);
            }
        }

        String licenseTier = null;
        long licenseDaysRemaining = 0;
        Map<String, Object> limits = Map.of();
        Map<String, Object> features = Map.of();

        var licenseOpt = licenseService.getActiveLicense(tenant.getId());
        if (licenseOpt.isPresent()) {
            LicenseEntity lic = licenseOpt.get();
            licenseTier = lic.getTier();
            licenseDaysRemaining = daysUntil(lic.getExpiresAt());
            limits = lic.getLimits() != null ? lic.getLimits() : Map.of();
            features = lic.getFeatures() != null ? lic.getFeatures() : Map.of();
        }

        return new DashboardData(
                tenant.getName(), tenant.getSlug(),
                tenant.getTier().name(), tenant.getStatus().name(),
                serverHealthy, serverStatus, endpoint,
                licenseTier, licenseDaysRemaining,
                limits, features, agentCount, environmentCount
        );
    }

    public LicenseData getLicense() {
        TenantEntity tenant = resolveTenant();
        return licenseService.getActiveLicense(tenant.getId())
                .map(lic -> new LicenseData(
                        lic.getId(), lic.getTier(),
                        lic.getFeatures() != null ? lic.getFeatures() : Map.of(),
                        lic.getLimits() != null ? lic.getLimits() : Map.of(),
                        lic.getIssuedAt(), lic.getExpiresAt(),
                        lic.getToken(), daysUntil(lic.getExpiresAt())
                ))
                .orElse(null);
    }

    public List<Map<String, Object>> listTeamMembers() {
        TenantEntity tenant = resolveTenant();
        String orgId = tenant.getLogtoOrgId();
        if (orgId == null || orgId.isBlank()) {
            return List.of();
        }
        return logtoClient.listOrganizationMembers(orgId);
    }

    public String inviteTeamMember(String email, String roleId) {
        TenantEntity tenant = resolveTenant();
        String orgId = tenant.getLogtoOrgId();
        if (orgId == null || orgId.isBlank()) {
            throw new IllegalStateException("Tenant has no Logto organization configured");
        }
        return logtoClient.createAndInviteUser(email, orgId, roleId);
    }

    public void removeTeamMember(String userId) {
        TenantEntity tenant = resolveTenant();
        String orgId = tenant.getLogtoOrgId();
        if (orgId == null || orgId.isBlank()) {
            throw new IllegalStateException("Tenant has no Logto organization configured");
        }
        logtoClient.removeUserFromOrganization(orgId, userId);
    }

    public void changeTeamMemberRole(String userId, String roleId) {
        TenantEntity tenant = resolveTenant();
        String orgId = tenant.getLogtoOrgId();
        if (orgId == null || orgId.isBlank()) {
            throw new IllegalStateException("Tenant has no Logto organization configured");
        }
        logtoClient.assignOrganizationRole(orgId, userId, roleId);
    }

    public void resetServerAdminPassword(String newPassword) {
        TenantEntity tenant = resolveTenant();
        String endpoint = tenant.getServerEndpoint();
        if (endpoint == null || endpoint.isBlank()) {
            throw new IllegalStateException("Server not provisioned yet");
        }
        if (newPassword == null || newPassword.length() < 8) {
            throw new IllegalArgumentException("Password must be at least 8 characters");
        }
        serverApiClient.resetServerAdminPassword(endpoint, newPassword);
    }

    public void changePassword(String userId, String newPassword) {
        if (newPassword == null || newPassword.length() < 8) {
            throw new IllegalArgumentException("Password must be at least 8 characters");
        }
        logtoClient.updateUserPassword(userId, newPassword);
    }

    public void resetTeamMemberPassword(String userId, String newPassword) {
        TenantEntity tenant = resolveTenant();
        String orgId = tenant.getLogtoOrgId();
        if (orgId == null || orgId.isBlank()) {
            throw new IllegalStateException("Tenant has no Logto organization configured");
        }
        // Verify the target user belongs to this tenant's org
        var members = logtoClient.listOrganizationMembers(orgId);
        boolean isMember = members.stream()
                .anyMatch(m -> userId.equals(String.valueOf(m.get("id"))));
        if (!isMember) {
            throw new IllegalArgumentException("User is not a member of this organization");
        }
        if (newPassword == null || newPassword.length() < 8) {
            throw new IllegalArgumentException("Password must be at least 8 characters");
        }
        logtoClient.updateUserPassword(userId, newPassword);
    }

    public TenantSettingsData getSettings() {
        TenantEntity tenant = resolveTenant();
        String publicEndpoint = provisioningProps.publicProtocol() + "://"
                + provisioningProps.publicHost() + "/t/" + tenant.getSlug() + "/";
        return new TenantSettingsData(
                tenant.getName(), tenant.getSlug(),
                tenant.getTier().name(), tenant.getStatus().name(),
                publicEndpoint, tenant.getCreatedAt()
        );
    }

    public void restartServer() {
        TenantEntity tenant = resolveTenant();
        if (!tenantProvisioner.isAvailable()) return;

        tenantProvisioner.stop(tenant.getSlug());
        try {
            tenantProvisioner.start(tenant.getSlug());
        } catch (RuntimeException e) {
            if (e.getMessage() != null && e.getMessage().contains("re-provision required")) {
                log.info("Containers missing for '{}' — re-provisioning", tenant.getSlug());
                tenantProvisioner.remove(tenant.getSlug());
                var license = licenseService.getActiveLicense(tenant.getId()).orElse(null);
                String token = license != null ? license.getToken() : "";
                vendorTenantService.provisionAsync(
                    tenant.getId(), tenant.getSlug(), tenant.getTier().name(), token, null);
                return;
            }
            throw e;
        }
    }

    public void upgradeServer() {
        TenantEntity tenant = resolveTenant();
        if (!tenantProvisioner.isAvailable()) return;

        tenantProvisioner.upgrade(tenant.getSlug());

        var license = licenseService.getActiveLicense(tenant.getId()).orElse(null);
        String token = license != null ? license.getToken() : "";
        vendorTenantService.provisionAsync(
            tenant.getId(), tenant.getSlug(), tenant.getTier().name(), token, null);
    }
}
