import {
    toBackendStatus,
    toFrontendStatus,
    type CategoryOption,
    type MinistryOption,
    type RgcDecisionRow,
    type RgcDecisionStatus,
    type RgcDecisionStatusCode,
} from "../rgc-decision-data";

const API_BASE_URL = (
    process.env.NEXT_PUBLIC_API_URL ??
    process.env.NEXT_PUBLIC_API_BASE_URL ??
    "http://localhost:3001/api/v1"
).replace(/\/$/, "");

type ApiMessage = string | string[] | undefined;

type ApiResponse<T> = {
    success?: boolean;
    statusCode?: number;
    message?: ApiMessage;
    data?: T;
};

type ApiListData<T> = {
    items?: T[];
    meta?: {
        total?: number;
        page?: number;
        limit?: number;
        totalPages?: number;
    };
};

type ApiNestedListData<T> = {
    data?: ApiListData<T> | T[];
    items?: T[];
};

type ApiListResponse<T> = ApiResponse<
    ApiListData<T> | T[] | ApiNestedListData<T>
> & {
    items?: T[];
    meta?: {
        total?: number;
        page?: number;
        limit?: number;
        totalPages?: number;
    };
};

type ApiAuthUser = {
    id?: number | string;
    userId?: number | string;
    sub?: number | string;
};

type ApiAuthMeResponse = {
    id?: number | string;
    userId?: number | string;
    sub?: number | string;

    user?: ApiAuthUser;

    data?: {
        id?: number | string;
        userId?: number | string;
        sub?: number | string;
        user?: ApiAuthUser;
    };

    message?: ApiMessage;
};

type JwtPayload = {
    id?: number | string;
    userId?: number | string;
    sub?: number | string;

    user?: {
        id?: number | string;
        userId?: number | string;
    };
};

type ApiStakeholder = {
    id: number;
    name?: string | null;
    shortName?: string | null;
    acronym?: string | null;
    logo?: string | null;
};

type ApiCategory = {
    id: number;
    name?: string | null;
};

export type IndicatorOption = {
    id: number;
    name: string;
    description?: string | null;
};

type ApiIndicator = {
    id: number;
    name?: string | null;
    description?: string | null;
};

type ApiUser = {
    id?: number;
    name?: string | null;
    email?: string | null;
};

export type PlenaryOption = {
    id: number;
    name: string;
};

type ApiPlenary = {
    id: number;
    name?: string | null;
    title?: string | null;
    description?: string | null;
};

type ApiRgcDecision = {
    id: number;

    plenaryId?: number | null;
    plenary?: ApiPlenary | null;

    stakeholderId?: number;
    stakeholder?: ApiStakeholder | null;
    ministry?: string | null;

    categoryId?: number;
    category?: string | ApiCategory | null;
    categoryInfo?: ApiCategory | null;

    indicatorId?: number | null;
    indicator?: ApiIndicator | string | null;
    indicatorName?: string | null;
    indicatorDescription?: string | null;

    meetingDate?: string | null;

    status?: string | null;
    statusCode?: string | null;

    focalPerson?: string | null;
    decision?: string | null;

    verificationSource?: string | null;
    sourceOfVerification?: string | null;
    verificationLink?: string | null;

    createdBy?: ApiUser | null;
    createdAt?: string | null;
    updatedAt?: string | null;
};

export type CreateCdcRgcDecisionInput = {
    stakeholderId: number;
    ministry: string;

    plenaryId: number;
    plenary: string;

    categoryId: number;
    category: string;

    indicatorId: number;
    indicator: string;

    decision: string;
    meetingDate: string;
    status: RgcDecisionStatus;

    focalPerson: string;
    sourceOfVerification: string;
    verificationLink: string;
};

function toValidUserId(value: unknown): string {
    if (
        typeof value !== "string" &&
        typeof value !== "number"
    ) {
        return "";
    }

    const parsed = Number(value);

    if (
        !Number.isInteger(parsed) ||
        parsed < 1
    ) {
        return "";
    }

    return String(parsed);
}

function readStorageValue(
    key: string,
): string | null {
    if (typeof window === "undefined") {
        return null;
    }

    return (
        window.localStorage.getItem(key) ??
        window.sessionStorage.getItem(key)
    );
}

function getStoredToken(): string {
    if (typeof window === "undefined") {
        return "";
    }

    const tokenKeys = [
        "accessToken",
        "access_token",
        "token",
        "authToken",
        "auth_token",
        "jwt",
        "jwtToken",
        "bearerToken",
    ];

    for (const key of tokenKeys) {
        const token = readStorageValue(key)?.trim();

        if (token) {
            return token.replace(/^Bearer\s+/i, "");
        }
    }

    const objectKeys = [
        "auth",
        "session",
        "user",
        "authUser",
        "currentUser",
        "profile",
    ];

    for (const key of objectKeys) {
        const rawValue = readStorageValue(key);

        if (!rawValue) {
            continue;
        }

        try {
            const parsed = JSON.parse(rawValue) as {
                token?: string;
                accessToken?: string;
                access_token?: string;

                data?: {
                    token?: string;
                    accessToken?: string;
                    access_token?: string;
                };
            };

            const token =
                parsed.accessToken ??
                parsed.access_token ??
                parsed.token ??
                parsed.data?.accessToken ??
                parsed.data?.access_token ??
                parsed.data?.token;

            if (token?.trim()) {
                return token.trim().replace(/^Bearer\s+/i, "");
            }
        } catch {
            // Ignore invalid storage JSON.
        }
    }

    return "";
}

function decodeJwtPayload(
    token: string,
): JwtPayload | null {
    if (
        typeof window === "undefined" ||
        !token
    ) {
        return null;
    }

    const parts = token.split(".");

    if (parts.length < 2) {
        return null;
    }

    try {
        const base64 = parts[1]
            .replace(/-/g, "+")
            .replace(/_/g, "/");

        const padded = base64.padEnd(
            Math.ceil(base64.length / 4) * 4,
            "=",
        );

        const decoded = window.atob(padded);

        const json = decodeURIComponent(
            Array.from(decoded)
                .map(
                    (character) =>
                        `%${character
                            .charCodeAt(0)
                            .toString(16)
                            .padStart(2, "0")}`,
                )
                .join(""),
        );

        return JSON.parse(json) as JwtPayload;
    } catch {
        return null;
    }
}

function getUserIdFromJwt(): string {
    const token = getStoredToken();
    const payload = decodeJwtPayload(token);

    if (!payload) {
        return "";
    }

    return (
        toValidUserId(payload.id) ||
        toValidUserId(payload.userId) ||
        toValidUserId(payload.sub) ||
        toValidUserId(payload.user?.id) ||
        toValidUserId(payload.user?.userId)
    );
}

function getStoredUserId(): string {
    if (typeof window === "undefined") {
        return "";
    }

    const directKeys = [
        "userId",
        "user_id",
        "currentUserId",
        "authUserId",
        "auth_user_id",
        "current_user_id",
        "loginUserId",
    ];

    for (const key of directKeys) {
        const userId = toValidUserId(
            readStorageValue(key),
        );

        if (userId) {
            return userId;
        }
    }

    const objectKeys = [
        "user",
        "authUser",
        "currentUser",
        "auth",
        "profile",
        "session",
        "loginUser",
        "account",
    ];

    for (const key of objectKeys) {
        const rawValue = readStorageValue(key);

        if (!rawValue) {
            continue;
        }

        try {
            const parsed = JSON.parse(rawValue) as {
                id?: unknown;
                userId?: unknown;
                sub?: unknown;

                user?: {
                    id?: unknown;
                    userId?: unknown;
                    sub?: unknown;
                };

                data?: {
                    id?: unknown;
                    userId?: unknown;
                    sub?: unknown;

                    user?: {
                        id?: unknown;
                        userId?: unknown;
                        sub?: unknown;
                    };
                };
            };

            const userId =
                toValidUserId(parsed.id) ||
                toValidUserId(parsed.userId) ||
                toValidUserId(parsed.sub) ||
                toValidUserId(parsed.user?.id) ||
                toValidUserId(parsed.user?.userId) ||
                toValidUserId(parsed.user?.sub) ||
                toValidUserId(parsed.data?.id) ||
                toValidUserId(parsed.data?.userId) ||
                toValidUserId(parsed.data?.sub) ||
                toValidUserId(parsed.data?.user?.id) ||
                toValidUserId(parsed.data?.user?.userId) ||
                toValidUserId(parsed.data?.user?.sub);

            if (userId) {
                return userId;
            }
        } catch {
            // Ignore invalid storage JSON.
        }
    }

    return getUserIdFromJwt();
}

async function readJson<T>(
    response: Response,
): Promise<T> {
    const text = await response.text();

    if (!text) {
        return {} as T;
    }

    try {
        return JSON.parse(text) as T;
    } catch {
        throw new Error(
            "Backend returned an invalid JSON response.",
        );
    }
}

async function getUserIdFromAuthMe(): Promise<string> {
    const token = getStoredToken();

    try {
        const response = await fetch(
            `${API_BASE_URL}/auth/me`,
            {
                method: "GET",
                headers: {
                    Accept: "application/json",

                    ...(token
                        ? {
                            Authorization: `Bearer ${token}`,
                        }
                        : {}),
                },
                credentials: "include",
                cache: "no-store",
            },
        );

        if (!response.ok) {
            return "";
        }

        const result =
            await readJson<ApiAuthMeResponse>(
                response,
            );

        return (
            toValidUserId(result.data?.user?.id) ||
            toValidUserId(
                result.data?.user?.userId,
            ) ||
            toValidUserId(result.data?.user?.sub) ||
            toValidUserId(result.data?.id) ||
            toValidUserId(result.data?.userId) ||
            toValidUserId(result.data?.sub) ||
            toValidUserId(result.user?.id) ||
            toValidUserId(result.user?.userId) ||
            toValidUserId(result.user?.sub) ||
            toValidUserId(result.id) ||
            toValidUserId(result.userId) ||
            toValidUserId(result.sub)
        );
    } catch {
        return "";
    }
}

async function getCurrentUserId(): Promise<string> {
    // Prefer the authenticated backend session so a stale localStorage ID
    // or a hard-coded development user cannot cause a 403 response.
    const authUserId = await getUserIdFromAuthMe();

    if (authUserId) {
        return authUserId;
    }

    const storedUserId = getStoredUserId();

    if (storedUserId) {
        return storedUserId;
    }

    const environmentUserId = toValidUserId(
        process.env.NEXT_PUBLIC_CDC_GPSF_USER_ID,
    );

    if (environmentUserId) {
        return environmentUserId;
    }

    throw new Error(
        "Authenticated CDC G-PSF user ID was not found. Please login again.",
    );
}

async function getHeaders(
    includeContentType = false,
): Promise<HeadersInit> {
    const userId = await getCurrentUserId();
    const token = getStoredToken();

    return {
        Accept: "application/json",
        "x-user-id": userId,

        ...(includeContentType
            ? {
                "Content-Type": "application/json",
            }
            : {}),

        ...(token
            ? {
                Authorization: `Bearer ${token}`,
            }
            : {}),
    };
}

function getErrorMessage(
    message: ApiMessage,
    fallback = "Request failed.",
): string {
    if (Array.isArray(message)) {
        return message.join(", ");
    }

    if (
        typeof message === "string" &&
        message.trim()
    ) {
        return message;
    }

    return fallback;
}

function extractItems<T>(
    payload: ApiListResponse<T>,
): T[] {
    if (Array.isArray(payload.data)) {
        return payload.data;
    }

    if (
        payload.data &&
        typeof payload.data === "object"
    ) {
        if (
            "items" in payload.data &&
            Array.isArray(payload.data.items)
        ) {
            return payload.data.items;
        }

        if ("data" in payload.data) {
            const nestedData = payload.data.data;

            if (Array.isArray(nestedData)) {
                return nestedData;
            }

            if (
                nestedData &&
                typeof nestedData === "object" &&
                "items" in nestedData &&
                Array.isArray(nestedData.items)
            ) {
                return nestedData.items;
            }
        }
    }

    if (Array.isArray(payload.items)) {
        return payload.items;
    }

    return [];
}

function formatDate(
    value?: string | null,
): string {
    if (!value) {
        return "-";
    }

    const text = String(value);

    if (/^\d{4}-\d{2}-\d{2}/.test(text)) {
        return text.slice(0, 10);
    }

    const date = new Date(value);

    if (Number.isNaN(date.getTime())) {
        return text;
    }

    return date.toISOString().slice(0, 10);
}

function getMinistryName(
    item: ApiRgcDecision,
): string {
    return (
        item.stakeholder?.shortName?.trim() ||
        item.stakeholder?.acronym?.trim() ||
        item.stakeholder?.name?.trim() ||
        item.ministry?.trim() ||
        "No data"
    );
}

function getCategoryName(
    item: ApiRgcDecision,
): string {
    if (
        typeof item.category === "string" &&
        item.category.trim()
    ) {
        return item.category.trim();
    }

    if (
        typeof item.category === "object" &&
        item.category?.name?.trim()
    ) {
        return item.category.name.trim();
    }

    return (
        item.categoryInfo?.name?.trim() ||
        "No data"
    );
}


function getIndicatorName(
    item: ApiRgcDecision,
): string {
    if (
        typeof item.indicator === "string" &&
        item.indicator.trim()
    ) {
        return item.indicator.trim();
    }

    if (
        typeof item.indicator === "object" &&
        item.indicator?.name?.trim()
    ) {
        return item.indicator.name.trim();
    }

    if (item.indicatorName?.trim()) {
        return item.indicatorName.trim();
    }

    return "-";
}

function getStatusCode(
    item: ApiRgcDecision,
): RgcDecisionStatusCode {
    const normalized = String(
        item.statusCode ?? item.status ?? "",
    )
        .trim()
        .toUpperCase()
        .replace(/[\s-]+/g, "_");

    if (normalized === "SOLVED") {
        return "SOLVED";
    }

    if (normalized === "IN_PROGRESS") {
        return "IN_PROGRESS";
    }

    return "NOT_ADDRESSED";
}

function mapApiItem(
    item: ApiRgcDecision,
    index = 0,
): RgcDecisionRow {
    const statusCode = getStatusCode(item);

    return {
        id: Number(item.id) || index + 1,

        plenaryId:
            item.plenaryId === null ||
                item.plenaryId === undefined
                ? null
                : Number(item.plenaryId),

        stakeholderId: Number(
            item.stakeholderId ??
            item.stakeholder?.id ??
            0,
        ),

        ministry: getMinistryName(item),
        ministryLogo:
            item.stakeholder?.logo ?? null,

        categoryId: Number(
            item.categoryId ??
            item.categoryInfo?.id ??
            (typeof item.category === "object"
                ? item.category?.id
                : 0) ??
            0,
        ),

        category: getCategoryName(item),

        decision:
            item.decision?.trim() || "No data",

        meetingDate:
            formatDate(item.meetingDate),

        status:
            toFrontendStatus(statusCode),

        statusCode,

        indicator: getIndicatorName(item),

        focalPerson:
            item.focalPerson?.trim() ||
            "No data",

        sourceOfVerification:
            item.sourceOfVerification?.trim() ||
            item.verificationSource?.trim() ||
            "No data",

        verificationLink:
            item.verificationLink?.trim() ||
            "",

        createdAt:
            item.createdAt ?? undefined,

        updatedAt:
            item.updatedAt ?? undefined,
    };
}

export async function getCdcRgcDecisions(): Promise<
    RgcDecisionRow[]
> {
    const params = new URLSearchParams({
        page: "1",
        limit: "100",
    });

    const response = await fetch(
        `${API_BASE_URL}/rgc-decisions/cdc-gpsf?${params.toString()}`,
        {
            method: "GET",
            headers: await getHeaders(),
            credentials: "include",
            cache: "no-store",
        },
    );

    const payload =
        await readJson<
            ApiListResponse<ApiRgcDecision>
        >(response);

    if (!response.ok) {
        throw new Error(
            getErrorMessage(
                payload.message,
                "Unable to load CDC G-PSF RGC Decisions.",
            ),
        );
    }

    return extractItems(payload).map(mapApiItem);
}

async function fetchIndicatorOptions(
    headers: HeadersInit,
): Promise<ApiListResponse<ApiIndicator>> {
    const endpoints = [
        `${API_BASE_URL}/indicators/options`,
        `${API_BASE_URL}/indicators?limit=100`,
        `${API_BASE_URL}/indicators`,
        `${API_BASE_URL}/rgc-decisions/lookups/indicators`,
    ];

    let lastError = "Unable to load Indicators.";

    for (const endpoint of endpoints) {
        try {
            const response = await fetch(endpoint, {
                method: "GET",
                headers,
                credentials: "include",
                cache: "no-store",
            });

            const payload =
                await readJson<
                    ApiListResponse<ApiIndicator>
                >(response);

            if (!response.ok) {
                lastError = getErrorMessage(
                    payload.message,
                    `Indicator API failed: ${response.status}`,
                );
                continue;
            }

            const items = extractItems(payload);

            if (items.length > 0) {
                return payload;
            }

            lastError =
                "Indicator API returned an empty list.";
        } catch (error) {
            lastError =
                error instanceof Error
                    ? error.message
                    : "Unable to load Indicators.";
        }
    }

    throw new Error(lastError);
}


async function fetchPlenaryOptions(
    headers: HeadersInit,
): Promise<ApiListResponse<ApiPlenary>> {
    const endpoints = [
        `${API_BASE_URL}/rgc-decisions/lookups/plenaries`,
        `${API_BASE_URL}/plenaries/options`,
        `${API_BASE_URL}/plenaries?limit=100`,
        `${API_BASE_URL}/plenaries`,
    ];

    let lastError = "Unable to load Plenaries.";

    for (const endpoint of endpoints) {
        try {
            const response = await fetch(endpoint, {
                method: "GET",
                headers,
                credentials: "include",
                cache: "no-store",
            });

            const payload =
                await readJson<
                    ApiListResponse<ApiPlenary>
                >(response);

            if (!response.ok) {
                lastError = getErrorMessage(
                    payload.message,
                    `Plenary API failed: ${response.status}`,
                );
                continue;
            }

            const items = extractItems(payload);

            if (items.length > 0) {
                return payload;
            }

            lastError =
                "Plenary API returned an empty list.";
        } catch (error) {
            lastError =
                error instanceof Error
                    ? error.message
                    : "Unable to load Plenaries.";
        }
    }

    throw new Error(lastError);
}

export async function getCdcRgcDecisionLookups(): Promise<{
    ministries: MinistryOption[];
    plenaries: PlenaryOption[];
    categories: CategoryOption[];
    indicators: IndicatorOption[];
}> {
    const headers = await getHeaders();

    const [
        ministriesResponse,
        plenariesPayload,
        categoriesResponse,
        indicatorsPayload,
    ] = await Promise.all([
        fetch(
            `${API_BASE_URL}/rgc-decisions/lookups/ministries`,
            {
                method: "GET",
                headers,
                credentials: "include",
                cache: "no-store",
            },
        ),
        fetchPlenaryOptions(headers),
        fetch(
            `${API_BASE_URL}/rgc-decisions/lookups/categories`,
            {
                method: "GET",
                headers,
                credentials: "include",
                cache: "no-store",
            },
        ),
        fetchIndicatorOptions(headers),
    ]);

    const [
        ministriesPayload,
        categoriesPayload,
    ] = await Promise.all([
        readJson<ApiListResponse<ApiStakeholder>>(
            ministriesResponse,
        ),
        readJson<ApiListResponse<ApiCategory>>(
            categoriesResponse,
        ),
    ]);

    if (!ministriesResponse.ok) {
        throw new Error(
            getErrorMessage(
                ministriesPayload.message,
                "Unable to load Ministries.",
            ),
        );
    }

    if (!categoriesResponse.ok) {
        throw new Error(
            getErrorMessage(
                categoriesPayload.message,
                "Unable to load Categories.",
            ),
        );
    }

    const ministries = extractItems(
        ministriesPayload,
    )
        .map((item) => ({
            id: Number(item.id),
            name:
                item.shortName?.trim() ||
                item.acronym?.trim() ||
                item.name?.trim() ||
                `Ministry ${item.id}`,
            logo: item.logo ?? null,
        }))
        .filter(
            (item) =>
                Number.isInteger(item.id) &&
                item.id > 0,
        );

    const plenaries = extractItems(
        plenariesPayload,
    )
        .map((item) => ({
            id: Number(item.id),
            name:
                item.name?.trim() ||
                item.title?.trim() ||
                `Plenary ${item.id}`,
        }))
        .filter(
            (item) =>
                Number.isInteger(item.id) &&
                item.id > 0,
        );

    const categories = extractItems(
        categoriesPayload,
    )
        .map((item) => ({
            id: Number(item.id),
            name:
                item.name?.trim() ||
                `Category ${item.id}`,
        }))
        .filter(
            (item) =>
                Number.isInteger(item.id) &&
                item.id > 0,
        );

    const indicators = extractItems(
        indicatorsPayload,
    )
        .map((item) => ({
            id: Number(item.id),
            name:
                item.name?.trim() ||
                `Indicator ${item.id}`,
            description:
                item.description?.trim() || null,
        }))
        .filter(
            (item) =>
                Number.isInteger(item.id) &&
                item.id > 0 &&
                Boolean(item.name),
        );

    return {
        ministries,
        plenaries,
        categories,
        indicators,
    };
}

export async function createCdcRgcDecision(
    input: CreateCdcRgcDecisionInput,
): Promise<RgcDecisionRow> {
    if (
        !Number.isInteger(input.stakeholderId) ||
        input.stakeholderId < 1
    ) {
        throw new Error(
            "Please select a Ministry.",
        );
    }

    if (
        !Number.isInteger(input.plenaryId) ||
        input.plenaryId < 1
    ) {
        throw new Error(
            "Please select a Plenary.",
        );
    }

    if (
        !Number.isInteger(input.categoryId) ||
        input.categoryId < 1
    ) {
        throw new Error(
            "Please select a Category.",
        );
    }

    if (
        !Number.isInteger(input.indicatorId) ||
        input.indicatorId < 1
    ) {
        throw new Error(
            "Please select an Indicator.",
        );
    }

    if (!input.meetingDate.trim()) {
        throw new Error(
            "Meeting Date is required.",
        );
    }

    if (!input.focalPerson.trim()) {
        throw new Error(
            "Focal Person is required.",
        );
    }

    if (!input.decision.trim()) {
        throw new Error(
            "RGC Decision is required.",
        );
    }

    if (
        !input.sourceOfVerification.trim()
    ) {
        throw new Error(
            "Source of Verification is required.",
        );
    }

    const response = await fetch(
        `${API_BASE_URL}/rgc-decisions/cdc-gpsf`,
        {
            method: "POST",
            headers: await getHeaders(true),
            credentials: "include",
            cache: "no-store",

            body: JSON.stringify({
                stakeholderId:
                    input.stakeholderId,

                plenaryId:
                    input.plenaryId,

                categoryId:
                    input.categoryId,

                indicatorId:
                    input.indicatorId,

                meetingDate:
                    input.meetingDate,

                status:
                    toBackendStatus(input.status),

                focalPerson:
                    input.focalPerson.trim(),

                decision:
                    input.decision.trim(),

                verificationSource:
                    input.sourceOfVerification.trim(),

                verificationLink:
                    input.verificationLink.trim(),
            }),
        },
    );

    const payload =
        await readJson<
            ApiResponse<ApiRgcDecision>
        >(response);

    if (!response.ok) {
        throw new Error(
            getErrorMessage(
                payload.message,
                "Unable to create RGC Decision.",
            ),
        );
    }

    const item = payload.data;

    if (!item?.id) {
        throw new Error(
            "Created RGC Decision was not returned from backend.",
        );
    }

    return mapApiItem(item);
}