-- Daily news digest schema.
-- PostgreSQL 12+; no application credentials are stored here.

BEGIN;

CREATE TABLE IF NOT EXISTS news_sources (
    id              BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name            VARCHAR(200) NOT NULL,
    homepage_url    TEXT,
    feed_url        TEXT NOT NULL,
    region          VARCHAR(20) NOT NULL DEFAULT 'international'
                    CHECK (region IN ('domestic', 'international')),
    category        VARCHAR(40) NOT NULL DEFAULT 'general'
                    CHECK (category IN (
                        'general', 'ai_agent', 'agri_hardware',
                        'agri_solutions', 'agri_services', 'policy_market'
                    )),
    priority        SMALLINT NOT NULL DEFAULT 50 CHECK (priority BETWEEN 0 AND 100),
    enabled         BOOLEAN NOT NULL DEFAULT TRUE,
    last_fetched_at TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_news_sources_feed_url UNIQUE (feed_url)
);

CREATE TABLE IF NOT EXISTS daily_reports (
    id              BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    report_date     DATE NOT NULL,
    title           VARCHAR(300) NOT NULL,
    introduction    TEXT,
    item_count      INTEGER NOT NULL DEFAULT 0 CHECK (item_count >= 0),
    status          VARCHAR(20) NOT NULL DEFAULT 'draft'
                    CHECK (status IN ('draft', 'published', 'failed')),
    email_status    VARCHAR(20) NOT NULL DEFAULT 'pending'
                    CHECK (email_status IN ('pending', 'sent', 'failed', 'skipped')),
    email_sent_at   TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_daily_reports_report_date UNIQUE (report_date)
);

CREATE TABLE IF NOT EXISTS news_articles (
    id              BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    source_id       BIGINT REFERENCES news_sources(id) ON DELETE SET NULL,
    external_id     TEXT,
    title           TEXT NOT NULL,
    summary         TEXT,
    url             TEXT NOT NULL,
    author          VARCHAR(300),
    language_code   VARCHAR(16) NOT NULL DEFAULT 'zh',
    translated_title TEXT,
    translated_summary TEXT,
    translation_status VARCHAR(20) NOT NULL DEFAULT 'not_needed'
                    CHECK (translation_status IN ('not_needed', 'pending', 'translated', 'failed', 'skipped')),
    translation_provider VARCHAR(80),
    translated_at   TIMESTAMPTZ,
    region          VARCHAR(20) NOT NULL DEFAULT 'international'
                    CHECK (region IN ('domestic', 'international')),
    category        VARCHAR(40) NOT NULL DEFAULT 'general'
                    CHECK (category IN (
                        'general', 'ai_agent', 'agri_hardware',
                        'agri_solutions', 'agri_services', 'policy_market'
                    )),
    topics          TEXT[] NOT NULL DEFAULT '{}',
    keywords        TEXT[] NOT NULL DEFAULT '{}',
    importance_score SMALLINT NOT NULL DEFAULT 50 CHECK (importance_score BETWEEN 0 AND 100),
    published_at    TIMESTAMPTZ,
    discovered_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    is_read         BOOLEAN NOT NULL DEFAULT FALSE,
    content_hash    CHAR(64),
    raw_payload     JSONB,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_news_articles_url UNIQUE (url),
    CONSTRAINT uq_news_articles_source_external UNIQUE (source_id, external_id)
);

CREATE TABLE IF NOT EXISTS report_articles (
    report_id       BIGINT NOT NULL REFERENCES daily_reports(id) ON DELETE CASCADE,
    article_id      BIGINT NOT NULL REFERENCES news_articles(id) ON DELETE CASCADE,
    section         VARCHAR(40) NOT NULL DEFAULT 'general'
                    CHECK (section IN (
                        'general', 'ai_agent', 'agri_hardware',
                        'agri_solutions', 'agri_services', 'policy_market'
                    )),
    display_order   INTEGER NOT NULL DEFAULT 0 CHECK (display_order >= 0),
    is_highlight    BOOLEAN NOT NULL DEFAULT FALSE,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (report_id, article_id)
);

CREATE TABLE IF NOT EXISTS email_deliveries (
    id              BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    report_id       BIGINT NOT NULL REFERENCES daily_reports(id) ON DELETE CASCADE,
    recipient_id    BIGINT,
    recipient       VARCHAR(320) NOT NULL,
    modules         TEXT[] NOT NULL DEFAULT '{}',
    status          VARCHAR(20) NOT NULL DEFAULT 'pending'
                    CHECK (status IN ('pending', 'sent', 'failed')),
    provider_id     TEXT,
    error_message   TEXT,
    sent_at         TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Web users. Never store a plaintext password; password_hash is produced by
-- the web service using Argon2id or bcrypt.
CREATE TABLE IF NOT EXISTS app_users (
    id                  BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email               VARCHAR(320) NOT NULL,
    display_name        VARCHAR(120),
    password_hash       TEXT,
    role                VARCHAR(20) NOT NULL DEFAULT 'user'
                        CHECK (role IN ('user', 'admin')),
    is_active           BOOLEAN NOT NULL DEFAULT TRUE,
    email_verified_at   TIMESTAMPTZ,
    last_login_at       TIMESTAMPTZ,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_app_users_email UNIQUE (email)
);

-- A user may have one or more receiving addresses. This also supports a
-- standalone subscription that is not linked to a web login (user_id NULL).
CREATE TABLE IF NOT EXISTS newsletter_recipients (
    id                  BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    user_id             BIGINT REFERENCES app_users(id) ON DELETE SET NULL,
    email               VARCHAR(320) NOT NULL,
    display_name        VARCHAR(120),
    enabled             BOOLEAN NOT NULL DEFAULT TRUE,
    send_time           TIME NOT NULL DEFAULT TIME '07:30',
    timezone            VARCHAR(64) NOT NULL DEFAULT 'Asia/Shanghai',
    send_all_modules    BOOLEAN NOT NULL DEFAULT FALSE,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_newsletter_recipients_email UNIQUE (email)
);

-- Module catalog. Add new modules here without changing the recipient table.
CREATE TABLE IF NOT EXISTS newsletter_modules (
    module_key          VARCHAR(40) PRIMARY KEY,
    module_name         VARCHAR(120) NOT NULL,
    description         TEXT,
    enabled             BOOLEAN NOT NULL DEFAULT TRUE,
    sort_order          INTEGER NOT NULL DEFAULT 0,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

INSERT INTO newsletter_modules (module_key, module_name, description, sort_order)
VALUES
    ('general', '综合热点', '国内外综合资讯', 10),
    ('ai_agent', 'AI 与 Agent', '大模型、AI 应用和 Agent', 20),
    ('agri_hardware', '农业硬件', '传感器、无人机、机器人和农机', 30),
    ('agri_solutions', '农业方案落地', '智慧农场、温室、灌溉和数字化方案', 40),
    ('agri_services', '农业服务', '农业 SaaS、农技、托管、供应链和运维服务', 50),
    ('policy_market', '政策与市场', '政策、补贴、投资、招标和市场动态', 60),
    ('domestic', '国内资讯', '国内来源或国内市场相关资讯', 70),
    ('international', '国际资讯', '国际来源或国际市场相关资讯', 80)
ON CONFLICT (module_key) DO NOTHING;

-- Many-to-many preference: one recipient can select any number of modules.
CREATE TABLE IF NOT EXISTS recipient_modules (
    recipient_id        BIGINT NOT NULL REFERENCES newsletter_recipients(id) ON DELETE CASCADE,
    module_key          VARCHAR(40) NOT NULL REFERENCES newsletter_modules(module_key) ON DELETE RESTRICT,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (recipient_id, module_key)
);

ALTER TABLE email_deliveries
    ADD COLUMN IF NOT EXISTS recipient_id BIGINT,
    ADD COLUMN IF NOT EXISTS modules TEXT[] NOT NULL DEFAULT '{}';

DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint WHERE conname = 'fk_email_deliveries_recipient'
    ) THEN
        ALTER TABLE email_deliveries
            ADD CONSTRAINT fk_email_deliveries_recipient
            FOREIGN KEY (recipient_id) REFERENCES newsletter_recipients(id) ON DELETE SET NULL;
    END IF;
END $$;

CREATE INDEX IF NOT EXISTS idx_articles_published_at
    ON news_articles (published_at DESC);
CREATE INDEX IF NOT EXISTS idx_articles_category_published
    ON news_articles (category, published_at DESC);
CREATE INDEX IF NOT EXISTS idx_articles_region_published
    ON news_articles (region, published_at DESC);
CREATE INDEX IF NOT EXISTS idx_articles_content_hash
    ON news_articles (content_hash);
CREATE INDEX IF NOT EXISTS idx_report_articles_order
    ON report_articles (report_id, section, display_order);
CREATE INDEX IF NOT EXISTS idx_articles_keywords_gin
    ON news_articles USING GIN (keywords);
CREATE INDEX IF NOT EXISTS idx_articles_topics_gin
    ON news_articles USING GIN (topics);
CREATE INDEX IF NOT EXISTS idx_newsletter_recipients_user
    ON newsletter_recipients (user_id);
CREATE INDEX IF NOT EXISTS idx_newsletter_recipients_enabled
    ON newsletter_recipients (enabled, send_time);
CREATE INDEX IF NOT EXISTS idx_recipient_modules_module
    ON recipient_modules (module_key);

-- The web app uses ILIKE for history searches. This is intentional because
-- PostgreSQL's built-in text configurations do not segment Chinese reliably.
-- A generated tsvector column is deliberately not used: some PostgreSQL
-- versions/drivers reject the expression as non-immutable.

COMMIT;

-- Optional starter sources. Review the URLs before inserting them.
-- INSERT INTO news_sources (name, homepage_url, feed_url, region, category, priority)
-- VALUES
-- ('Example source', 'https://example.com', 'https://example.com/feed.xml', 'international', 'general', 50);
